การคอมไพล์ตามเงื่อนไขและเป้าหมายเฟรมเวิร์ก


124

มีสถานที่เล็ก ๆ น้อย ๆ ที่โค้ดสำหรับโครงการของฉันอาจสามารถปรับปรุงได้อย่างมากหากกรอบเป้าหมายเป็นเวอร์ชันใหม่กว่า ฉันต้องการใช้ประโยชน์จากการรวบรวมแบบมีเงื่อนไขใน C # ให้ดีขึ้นเพื่อเปลี่ยนสิ่งเหล่านี้ตามต้องการ

สิ่งที่ต้องการ:

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

สัญลักษณ์เหล่านี้ฟรีหรือไม่? ฉันต้องฉีดสัญลักษณ์เหล่านี้เป็นส่วนหนึ่งของการกำหนดค่าโครงการหรือไม่ ดูเหมือนง่ายพอที่จะทำเพราะฉันจะรู้ว่าเฟรมเวิร์กใดถูกกำหนดเป้าหมายจาก MSBuild

/p:DefineConstants="NET40"

ผู้คนรับมือกับสถานการณ์นี้อย่างไร? คุณกำลังสร้างการกำหนดค่าที่แตกต่างกันหรือไม่? คุณส่งผ่านค่าคงที่ผ่านทางบรรทัดคำสั่งหรือไม่?



หากคุณต้องการการแก้ปัญหาก่อนอบที่เรียบง่ายใน VS โปรดขึ้นเสียงโหวตผู้ใช้นี้visualstudio.uservoice.com/forums/121579-visual-studio/...
JohnC

1
ลองดูที่ลิงค์นี้เช่นกัน อธิบายได้สวย blogs.msmvps.com/punitganshani/2015/06/21/…
มาร์โกอัลเวส

กลุ่มโปรเจ็กต์ nuget restore และกลุ่มอ้างอิง nuget ทางออกที่ดี: shazwazza.com/post/…
OzBob

คำตอบ:


119

วิธีที่ดีที่สุดวิธีหนึ่งในการทำสิ่งนี้คือการสร้างการกำหนดค่าการสร้างที่แตกต่างกันในโครงการของคุณ:

<PropertyGroup Condition="  '$(Framework)' == 'NET20' ">
  <DefineConstants>NET20</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>


<PropertyGroup Condition="  '$(Framework)' == 'NET35' ">
  <DefineConstants>NET35</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>

และในการกำหนดค่าเริ่มต้นของคุณ:

<Framework Condition=" '$(Framework)' == '' ">NET35</Framework>

ซึ่งจะตั้งค่าเริ่มต้นหากไม่ได้กำหนดไว้ที่อื่น ในกรณีข้างต้น OutputPath จะให้แอสเซมบลีแยกกันทุกครั้งที่คุณสร้างแต่ละเวอร์ชัน

จากนั้นสร้างเป้าหมาย AfterBuild เพื่อรวบรวมเวอร์ชันต่างๆของคุณ:

<Target Name="AfterBuild">
  <MSBuild Condition=" '$(Framework)' != 'NET20'"
    Projects="$(MSBuildProjectFile)"
    Properties="Framework=NET20"
    RunEachTargetSeparately="true"  />
</Target>

ตัวอย่างนี้จะคอมไพล์โครงการทั้งหมดอีกครั้งด้วยตัวแปร Framework ที่ตั้งค่าเป็น NET20 หลังจากบิลด์แรก (การคอมไพล์ทั้งสองอย่างและสมมติว่าบิลด์แรกคือ NET35 เริ่มต้นจากด้านบน) แต่ละคอมไพล์จะมีการกำหนดค่าตามเงื่อนไขที่กำหนดไว้อย่างถูกต้อง

ในลักษณะนี้คุณสามารถยกเว้นไฟล์บางไฟล์ในไฟล์โปรเจ็กต์ได้หากคุณต้องการโดยไม่ต้อง #ifdef ไฟล์:

<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />

หรือแม้แต่การอ้างอิง

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>

สมบูรณ์ ฉันมีประสบการณ์ในการแฮ็กรูปแบบ msbuild มากพอที่จะรู้ว่าสามารถทำได้ แต่ไม่มีเวลาเพียงพอที่จะหารายละเอียดทั้งหมด ขอบคุณมาก!
mckamey

หากคุณเพิ่มการอ้างอิงสำหรับคำตอบนี้ในคำถามที่เกี่ยวข้องของฉัน ( stackoverflow.com/questions/2923181 ) ฉันจะทำเครื่องหมายให้คุณเป็นคำตอบที่นั่น สิ่งนี้ช่วยแก้ปัญหาทั้งคู่ได้ในเวลาเดียวกัน
mckamey

7
ขอบคุณสำหรับคำตอบ แต่ตอนนี้ VS2010 มีแท็กใหม่ชื่อ "TargetFrameworkVersion" แล้วตอนนี้สำหรับแต่ละกลุ่มคุณสมบัติที่มีเงื่อนไขมีการเปลี่ยนแปลงเฉพาะ TargetFrameworkVersion เรายังจำเป็นต้องทำสิ่งเหล่านี้ทั้งหมดเพื่อให้ใช้งานได้หรือไม่
Akash Kava

คำตอบนี้ไม่ได้เป็นเพียงแค่การกำหนดค่าคงที่สำหรับเฟรมเวิร์กเท่านั้น แต่ยังสร้างสำหรับเฟรมเวิร์กหลาย ๆ เฟรมด้วย
katbyte

4
โพสต์นี้ใช้ได้ผลสำหรับฉัน แต่ฉันไม่เก่ง MSBuild และต้องใช้เวลาสักพักกว่าจะคิดออก ฉันทำโครงการที่ใช้งานได้เป็นตัวอย่าง dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6

44

อีกทางเลือกหนึ่งที่ใช้ได้ผลสำหรับฉันคือการเพิ่มสิ่งต่อไปนี้ลงในไฟล์โครงการ:

 <PropertyGroup>
    <DefineConstants Condition=" !$(DefineConstants.Contains(';NET')) ">$(DefineConstants);$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
    <DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
  </PropertyGroup>

ซึ่งจะใช้ค่าของคุณสมบัติ TargetFrameworkVersion ซึ่งเปรียบเสมือน "v3.5" แทนที่ "v" และ "" เพื่อรับ "NET35" (โดยใช้คุณสมบัติฟังก์ชันคุณสมบัติใหม่) จากนั้นจะลบค่า "NETxx" ที่มีอยู่และเพิ่มที่ส่วนท้ายของ DefinedConstants อาจเป็นไปได้ที่จะปรับปรุงสิ่งนี้ แต่ฉันไม่มีเวลาเล่นซอ

เมื่อมองไปที่แท็บ Build ของคุณสมบัติโปรเจ็กต์ใน VS คุณจะเห็นค่าผลลัพธ์ในส่วนสัญลักษณ์การคอมไพล์ตามเงื่อนไข การเปลี่ยนเวอร์ชันของเฟรมเวิร์กเป้าหมายบนแท็บแอปพลิเคชันจากนั้นเปลี่ยนสัญลักษณ์โดยอัตโนมัติ จากนั้นคุณสามารถใช้#if NETxxคำสั่งพรีโปรเซสเซอร์ได้ตามปกติ การเปลี่ยนโปรเจ็กต์ใน VS ดูเหมือนจะไม่สูญเสีย PropertyGroup ที่กำหนดเอง

โปรดทราบว่าสิ่งนี้ไม่ได้ให้อะไรแก่คุณสำหรับตัวเลือกเป้าหมายโปรไฟล์ลูกค้า แต่นั่นไม่ใช่ปัญหาสำหรับฉัน


Jeremy ว้าวขอบคุณที่นี่สมบูรณ์แบบเนื่องจากฉันกำลังสร้างแยกต่างหากในโซลูชันการสร้างของฉัน
Greg Finzer

+1 ใครจะคิดว่ามันจะยากขนาดนี้ที่จะหา "$ (DefineConstants.Contains ('... " ?? ขอบคุณ
CAD bloke

ในที่สุดฉันก็พบทางของฉันมาที่หน้านี้อีกครั้งเพราะฉันต้องการการทบทวนว่าฉันได้ค่าคงที่วิเศษเหล่านี้ในงานสร้างของฉันได้อย่างไร วันนี้ฉันกำลังทบทวนโครงการเดิมเพื่อแบ่งย่อยห้องสมุดและฉันต้องการสัญลักษณ์เพื่อไปร่วมกับฉันในส่วนย่อยบางส่วน ฉันเพิ่งดูด้านบนและสังเกตว่าคำตอบของคุณได้รับการยอมรับอย่างถูกต้องแล้วในไฟล์. CSPROJ ดั้งเดิม
David

15

ฉันมีปัญหากับวิธีแก้ปัญหาเหล่านี้อาจเป็นเพราะค่าคงที่เริ่มต้นของฉันถูกสร้างขึ้นล่วงหน้าโดยคุณสมบัติเหล่านี้

<DefineConstants />
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<DebugSymbols>true</DebugSymbols>

Visual Studio 2010 ยังเกิดข้อผิดพลาดเนื่องจากเครื่องหมายกึ่งโคลอนโดยอ้างว่าเป็นอักขระที่ผิดกฎหมาย ข้อความแสดงข้อผิดพลาดให้คำแนะนำแก่ฉันเนื่องจากฉันเห็นค่าคงที่ที่สร้างไว้ล่วงหน้าโดยคั่นด้วยเครื่องหมายจุลภาคตามด้วยเครื่องหมายอัฒภาคที่ "ผิดกฎหมาย" ในที่สุด หลังจากจัดรูปแบบใหม่และนวดแล้วฉันก็สามารถหาวิธีแก้ปัญหาที่เหมาะกับฉันได้

<PropertyGroup>
  <!-- Adding a custom constant will auto-magically append a comma and space to the pre-built constants.    -->
  <!-- Move the comma delimiter to the end of each constant and remove the trailing comma when we're done.  -->
  <DefineConstants Condition=" !$(DefineConstants.Contains(', NET')) ">$(DefineConstants)$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.Contains(', NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", NET"))))$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 2.0 ">$(DefineConstants)NET_20_OR_GREATER, </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 3.5 ">$(DefineConstants)NET_35_OR_GREATER</DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.EndsWith(', ')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", "))))</DefineConstants>
</PropertyGroup>

ฉันจะโพสต์ภาพหน้าจอของกล่องโต้ตอบการตั้งค่าคอมไพเลอร์ขั้นสูง (เปิดโดยคลิกปุ่ม "ตัวเลือกการคอมไพล์ขั้นสูง ... " บนแท็บคอมไพล์ของโครงการของคุณ) แต่ในฐานะผู้ใช้ใหม่ฉันไม่มีตัวแทนในการทำเช่นนั้น หากคุณเห็นภาพหน้าจอคุณจะเห็นค่าคงที่ที่กำหนดเองเติมโดยอัตโนมัติโดยกลุ่มคุณสมบัติจากนั้นคุณจะพูดว่า "ฉันต้องหาข้อมูลนั้นมาให้ฉัน"


แก้ไข:มีตัวแทนคนนั้นเร็วอย่างน่าประหลาดใจ .. ขอบคุณครับ! นี่คือภาพหน้าจอ:

การตั้งค่าคอมไพเลอร์ขั้นสูง


4

เริ่มต้นด้วยการล้างค่าคงที่:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

จากนั้นสร้างการดีบักการติดตามและค่าคงที่อื่น ๆ เช่น:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>

สุดท้ายสร้างค่าคงที่กรอบของคุณ:

<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
  <DefineConstants>NET10;NET20;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.0' ">
  <DefineConstants>NET10;NET20;NET30;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;NET45;$(DefineConstants)</DefineConstants>
</PropertyGroup>

ฉันคิดว่าแนวทางนี้น่าอ่านและเข้าใจได้มาก


3

ในไฟล์. csproj หลังจาก<DefineConstants>DEBUG;TRACE</DefineConstants>บรรทัดที่มีอยู่ให้เพิ่มสิ่งนี้:

<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '4.0' ">NET_40_EXACTLY</DefineConstants>

ทำสิ่งนี้สำหรับทั้งการกำหนดค่ารุ่น Debug และ Release จากนั้นใช้ในรหัสของคุณ:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif

3
พารามิเตอร์เริ่มต้นและตั้งชื่อไม่ใช่คุณลักษณะของ. NET framework 4 แต่เป็นคุณลักษณะของคอมไพเลอร์. NET 4 นอกจากนี้ยังสามารถใช้ในโปรเจ็กต์ที่กำหนดเป้าหมาย. NET 2 หรือ. NET 3 ได้ตราบใดที่คอมไพล์ใน Visual Studio 2010 มันเป็นเพียงแค่น้ำตาลในเชิงไวยากรณ์ ในทางกลับกันไดนามิกเป็นคุณลักษณะของ. NET framework 4 และคุณไม่สามารถใช้ในกรอบการกำหนดเป้าหมายโครงการก่อนหน้านี้ได้
Thanasis Ioannidis

2

@Azarien คำตอบของคุณสามารถใช้ร่วมกับ Jeremy เพื่อเก็บไว้ที่เดียวแทนที่จะเป็น Debug | Release เป็นต้น

สำหรับฉันการรวมทั้งสองรูปแบบเข้ากันได้ดีที่สุดเช่นรวมถึงเงื่อนไขในโค้ดโดยใช้ #if NETXX และยังสร้างสำหรับเวอร์ชันเฟรมเวิร์กที่แตกต่างกันในคราวเดียว

ฉันมีสิ่งเหล่านี้ในไฟล์. csproj ของฉัน:

  <PropertyGroup>
    <DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '3.5' ">
    <DefineConstants>NET35</DefineConstants>
    <OutputPath>bin\$(Configuration)\$(TargetFrameworkVersion)</OutputPath>
  </PropertyGroup>

และในเป้าหมาย:

  <Target Name="AfterBuild">
    <MSBuild Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' "
      Projects="$(MSBuildProjectFile)"
      Properties="TargetFrameworkVersion=v3.5"
      RunEachTargetSeparately="true"  />
  </Target>

0

หากคุณใช้ระบบบิลด์. NET Core คุณสามารถใช้สัญลักษณ์ที่กำหนดไว้ล่วงหน้า (ซึ่งตรงกับตัวอย่างของคุณอยู่แล้วและไม่ต้องการการเปลี่ยนแปลงใด ๆ กับของคุณ.csproj!):

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

รายการสัญลักษณ์ที่กำหนดไว้ล่วงหน้าได้รับการบันทึกไว้ในการพัฒนาไลบรารีด้วย Cross Platform Toolsและ#if (การอ้างอิง C #) :

.NET Framework: NETFRAMEWORK , NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471, NET472,NET48

.NET มาตรฐาน: NETSTANDARD , NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0,NETSTANDARD2_1

.NET Core: NETCOREAPP ` NETCOREAPP1_0` NETCOREAPP1_1, NETCOREAPP2_0` NETCOREAPP2_1` NETCOREAPP2_2,NETCOREAPP3_0

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.