Skip to content

Commit

Permalink
[msbuild/generator] Compile api definitions in MSBuild logic instead …
Browse files Browse the repository at this point in the history
…of inside the generator. (#18398#pullrequestreview-1498787955)

This has a few advantages:

* A step towards simplifying the generator.
* A step towards being able to build binding projects on Windows, since it's easier
  to build C# code in MSBuild using the Csc task rather than figuring out how to
  call csc as an external program (which we've already done on macOS, but having
  a single solution that works on all platforms is preferrable).

Note that this is only implemented for .NET, doing this for legacy Xamarin required a lot more work.
  • Loading branch information
rolfbjarne committed Jun 26, 2023
1 parent a36cd71 commit a8ba9c4
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 66 deletions.
28 changes: 14 additions & 14 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/BTouchTaskBase.cs
Expand Up @@ -23,8 +23,6 @@ public abstract class BTouchTaskBase : XamarinToolTask {
[Required]
public string BTouchToolExe { get; set; }

public string DotNetCscCompiler { get; set; }

public ITaskItem [] ObjectiveCLibraries { get; set; }

public ITaskItem [] AdditionalLibPaths { get; set; }
Expand All @@ -39,6 +37,8 @@ public abstract class BTouchTaskBase : XamarinToolTask {

public string AttributeAssembly { get; set; }

public ITaskItem CompiledApiDefinitionAssembly { get; set; }

public ITaskItem [] CoreSources { get; set; }

public string DefineConstants { get; set; }
Expand Down Expand Up @@ -79,9 +79,15 @@ public abstract class BTouchTaskBase : XamarinToolTask {
get {
// Return the dotnet executable we're executing with.
var dotnet_path = Environment.GetEnvironmentVariable ("DOTNET_HOST_PATH");
if (string.IsNullOrEmpty (dotnet_path))
throw new InvalidOperationException ($"DOTNET_HOST_PATH is not set");
return dotnet_path;
if (!string.IsNullOrEmpty (dotnet_path))
return dotnet_path;

if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
// This might happen when building from inside VS (design-time builds, etc.)
return "dotnet.exe";
}

throw new InvalidOperationException ($"DOTNET_HOST_PATH is not set");
}
}

Expand Down Expand Up @@ -123,6 +129,9 @@ protected override string GenerateCommandLineCommands ()
cmd.Add ("/v");
#endif

if (CompiledApiDefinitionAssembly is not null)
cmd.AddQuotedSwitchIfNotNull ("/compiled-api-definition-assembly:", CompiledApiDefinitionAssembly.ItemSpec);

cmd.Add ("/nostdlib");
cmd.AddQuotedSwitchIfNotNull ("/baselib:", BaseLibDll);
cmd.AddQuotedSwitchIfNotNull ("/out:", OutputAssembly);
Expand All @@ -144,14 +153,6 @@ protected override string GenerateCommandLineCommands ()
if (AllowUnsafeBlocks)
cmd.Add ("/unsafe");

if (!string.IsNullOrEmpty (DotNetCscCompiler)) {
var compileCommand = new string [] {
DotNetPath,
DotNetCscCompiler,
};
cmd.AddQuoted ("/compile-command:" + string.Join (" ", StringUtils.QuoteForProcess (compileCommand)));
}

cmd.AddQuotedSwitchIfNotNull ("/ns:", Namespace);

if (NoNFloatUsing)
Expand Down Expand Up @@ -266,7 +267,6 @@ public override bool Execute ()
BaseLibDll = PathUtils.ConvertToMacPath (BaseLibDll);
BTouchToolExe = PathUtils.ConvertToMacPath (BTouchToolExe);
BTouchToolPath = PathUtils.ConvertToMacPath (BTouchToolPath);
DotNetCscCompiler = PathUtils.ConvertToMacPath (DotNetCscCompiler);

if (IsDotNet) {
var customHome = Environment.GetEnvironmentVariable ("DOTNET_CUSTOM_HOME");
Expand Down
90 changes: 85 additions & 5 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Expand Up @@ -1602,6 +1602,11 @@ Copyright (C) 2018 Microsoft. All rights reserved.
$(_GenerateBindingsDependsOn);
</_GenerateBindingsDependsOn>

<_GenerateBindingsDependsOn Condition="'$(UsingAppleNETSdk)' == 'true'">
_CompileApiDefinitions;
$(_GenerateBindingsDependsOn);
</_GenerateBindingsDependsOn>

<_WriteGeneratorPropertiesDependsOn Condition="'$(UsingAppleNETSdk)' == 'true'">
$(_WriteGeneratorPropertiesDependsOn);
_ComputeBindingVariables;
Expand All @@ -1620,6 +1625,85 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<WriteLinesToFile File="$(OutputFilePath)" Lines="@(_ComputedRemoteGeneratorProperties)" />
</Target>

<PropertyGroup>
<_ComputeCompileApiDefinitionsInputsDependsOn Condition="'$(UsingAppleNETSdk)' == 'true'">
$(_ComputeCompileApiDefinitionsInputsDependsOn);
_ComputeBindingVariables;
</_ComputeCompileApiDefinitionsInputsDependsOn>
</PropertyGroup>

<Target Name="_ComputeCompileApiDefinitionsInputs" DependsOnTargets="$(_ComputeCompileApiDefinitionsInputsDependsOn)">
<ItemGroup>
<BTouchReferencePath Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
</ItemGroup>

<PropertyGroup>
<_CompiledApiDefinitionAssembly>$(DeviceSpecificIntermediateOutputPath)compiled-api-definitions.dll</_CompiledApiDefinitionAssembly>
<_CompiledApiDefinitionDefines>$(DefineConstants)</_CompiledApiDefinitionDefines>
<_CompiledApiDefinitionDefines Condition="'$(UsingAppleNETSdk)' == 'true'">$(_CompiledApiDefinitionDefines);NET</_CompiledApiDefinitionDefines>

<_CompiledApiDefinitionLibPaths>$(AdditionalLibPaths);$([System.IO.Path]::GetDirectoryName('$(BaseLibDllPath)'))</_CompiledApiDefinitionLibPaths>

<_CompiledApiDefinitionGlobalUsingsFile Condition="'$(UsingAppleNETSdk)' == 'true' And '$(NoNFloatUsing)' != 'true'">$(DeviceSpecificIntermediateOutputPath)compiled-api-definitions-usings.cs</_CompiledApiDefinitionGlobalUsingsFile>
</PropertyGroup>


<ItemGroup>
<_CompiledApiDefinitionReferences Include="$(_GeneratorAttributeAssembly)" />
<_CompiledApiDefinitionReferences Include="@(ReferencePath)" />
<_CompiledApiDefinitionReferences Include="@(BTouchReferencePath)" />

<_CompiledApiDefinitionsCompile Include="@(ObjcBindingApiDefinition)" />
<_CompiledApiDefinitionsCompile Include="@(ObjcBindingCoreSource)" />
</ItemGroup>

<WriteLinesToFile
Condition="'$(_CompiledApiDefinitionGlobalUsingsFile)' != ''"
File="$(_CompiledApiDefinitionGlobalUsingsFile)"
Lines="global using nfloat = global::System.Runtime.InteropServices.NFloat%3B"
Overwrite="true"
WriteOnlyWhenDifferent="true" />

<ItemGroup Condition="'$(_CompiledApiDefinitionGlobalUsingsFile)' != ''">
<_CompiledApiDefinitionsCompile Include="$(_CompiledApiDefinitionGlobalUsingsFile)" />
</ItemGroup>

</Target>

<Target Name="_CompileApiDefinitions"
Inputs="$(MSBuildAllProjects);
$(_CompiledApiDefinitionLibPaths);
$(_CompiledApiDefinitionGlobalUsingsFile);
@(_CompiledApiDefinitionReferences);
@(_CompiledApiDefinitionsCompile);"
Outputs="$(_CompiledApiDefinitionAssembly)"
DependsOnTargets="_ComputeCompileApiDefinitionsInputs"
Condition="'$(IsBindingProject)' == 'true' And '$(DesignTimeBuild)' != 'true'"
>

<!-- This is a mirror of the method GetCompiledApiBindingsAssembly in BindingTouch.cs where the compilation is done inside bgen -->
<Csc
AdditionalLibPaths="$(_CompiledApiDefinitionLibPaths)"
AllowUnsafeBlocks="true"
DebugType="portable"
DefineConstants="$(_CompiledApiDefinitionDefines)"
DisabledWarnings="436"
NoConfig="true"
NoStandardLib="true"
Deterministic="true"
OutputAssembly="$(_CompiledApiDefinitionAssembly)"
References="@(_CompiledApiDefinitionReferences)"
Sources="@(_CompiledApiDefinitionsCompile)"
TargetType="library"
ToolExe="$(CscToolExe)"
ToolPath="$(CscToolPath)"
UseHostCompilerIfAvailable="$(UseHostCompilerIfAvailable)"
UseSharedCompilation="$(UseSharedCompilation)"
VsSessionGuid="$(VsSessionGuid)"
>
</Csc>
</Target>

<Target Name="_GenerateBindings"
Inputs="$(MSBuildAllProjects);@(ObjcBindingApiDefinition);@(ObjcBindingCoreSource);@(ReferencePath);@(ObjcBindingNativeLibrary)"
Outputs="$(_GeneratedSourcesFileList)"
Expand All @@ -1628,10 +1712,6 @@ Copyright (C) 2018 Microsoft. All rights reserved.

<Warning Condition="'$(IsMacEnabled)' != 'true'" Text="It's currently not supported to build a binding project from Windows unless a connection to a Mac is available." />

<ItemGroup>
<BTouchReferencePath Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
</ItemGroup>

<PropertyGroup>
<BTouchEmitDebugInformation>false</BTouchEmitDebugInformation>
<BTouchEmitDebugInformation Condition="'$(Debug)' != ''">true</BTouchEmitDebugInformation>
Expand Down Expand Up @@ -1664,8 +1744,8 @@ Copyright (C) 2018 Microsoft. All rights reserved.
AttributeAssembly="$(_GeneratorAttributeAssembly)"
BaseLibDll="$(BaseLibDllPath)"
CoreSources="@(ObjcBindingCoreSource)"
CompiledApiDefinitionAssembly="$(_CompiledApiDefinitionAssembly)"
DefineConstants="$(DefineConstants)"
DotNetCscCompiler="$(_DotNetCscCompiler)"
EmitDebugInformation="$(BTouchEmitDebugInformation)"
ExtraArgs="$(BTouchExtraArgs)"
GeneratedSourcesDir="$(GeneratedSourcesDir)"
Expand Down
106 changes: 59 additions & 47 deletions src/bgen/BindingTouch.cs
Expand Up @@ -61,6 +61,8 @@ public class BindingTouch : IDisposable {
string []? compile_command = null;
string? baselibdll;
string? attributedll;
string compiled_api_definition_assembly = string.Empty;
bool noNFloatUsing;

List<string> libs = new List<string> ();
List<string> references = new List<string> ();
Expand Down Expand Up @@ -235,7 +237,6 @@ int Main3 (string [] args)
var defines = new List<string> ();
string? generate_file_list = null;
bool process_enums = false;
bool noNFloatUsing = false;

ErrorHelper.ClearWarningLevels ();

Expand Down Expand Up @@ -327,6 +328,7 @@ int Main3 (string [] args)
noNFloatUsing = string.Equals ("true", v, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty (v);
}
},
{ "compiled-api-definition-assembly=", "An assembly with the compiled api definitions.", (v) => compiled_api_definition_assembly = v },
new Mono.Options.ResponseFileSource (),
};

Expand Down Expand Up @@ -446,47 +448,7 @@ int Main3 (string [] args)
var paths = libs.Select ((v) => "-lib:" + v);

try {
var tmpass = Path.Combine (tmpdir, "temp.dll");

// -nowarn:436 is to avoid conflicts in definitions between core.dll and the sources
// Keep source files at the end of the command line - csc will create TWO assemblies if any sources preceed the -out parameter
var cargs = new List<string> ();

cargs.Add ("-debug");
cargs.Add ("-unsafe");
cargs.Add ("-target:library");
cargs.Add ("-nowarn:436");
cargs.Add ("-out:" + tmpass);
cargs.Add ("-r:" + GetAttributeLibraryPath ());
cargs.AddRange (refs);
if (unsafef)
cargs.Add ("-unsafe");
cargs.Add ("-r:" + baselibdll);
foreach (var def in defines)
cargs.Add ("-define:" + def);
#if NET
cargs.Add ("-define:NET");
#endif
cargs.AddRange (paths);
if (nostdlib) {
cargs.Add ("-nostdlib");
cargs.Add ("-noconfig");
}
cargs.AddRange (api_sources);
cargs.AddRange (core_sources);
if (!string.IsNullOrEmpty (Path.GetDirectoryName (baselibdll)))
cargs.Add ("-lib:" + Path.GetDirectoryName (baselibdll));

#if NET
var tmpusing = Path.Combine (tmpdir, "GlobalUsings.g.cs");
if (!noNFloatUsing) {
File.WriteAllText (tmpusing, "global using nfloat = global::System.Runtime.InteropServices.NFloat;\n");
cargs.Add (tmpusing);
}
#endif

Compile (cargs, 2);

var tmpass = GetCompiledApiBindingsAssembly (tmpdir, refs, nostdlib, api_sources, core_sources, defines, paths);
universe = new MetadataLoadContext (
new SearchPathsAssemblyResolver (
GetLibraryDirectories ().ToArray (),
Expand Down Expand Up @@ -595,7 +557,7 @@ int Main3 (string [] args)
return 0;
}

cargs.Clear ();
var cargs = new List<string> ();
if (unsafef)
cargs.Add ("-unsafe");
cargs.Add ("-target:library");
Expand All @@ -618,10 +580,7 @@ int Main3 (string [] args)
if (!string.IsNullOrEmpty (Path.GetDirectoryName (baselibdll)))
cargs.Add ("-lib:" + Path.GetDirectoryName (baselibdll));

#if NET
if (!noNFloatUsing)
cargs.Add (tmpusing);
#endif
AddNFloatUsing (cargs, tmpdir);

Compile (cargs, 1000);
} finally {
Expand All @@ -631,6 +590,59 @@ int Main3 (string [] args)
return 0;
}

// If anything is modified in this function, check if the _CompileApiDefinitions MSBuild target needs to be updated as well.
string GetCompiledApiBindingsAssembly (string tmpdir, IEnumerable<string> refs, bool nostdlib, List<string> api_sources, List<string> core_sources, List<string> defines, IEnumerable<string> paths)
{
if (!string.IsNullOrEmpty (compiled_api_definition_assembly))
return compiled_api_definition_assembly;

var tmpass = Path.Combine (tmpdir, "temp.dll");

// -nowarn:436 is to avoid conflicts in definitions between core.dll and the sources
// Keep source files at the end of the command line - csc will create TWO assemblies if any sources preceed the -out parameter
var cargs = new List<string> ();

cargs.Add ("-debug");
cargs.Add ("-unsafe");
cargs.Add ("-target:library");
cargs.Add ("-nowarn:436");
cargs.Add ("-out:" + tmpass);
cargs.Add ("-r:" + GetAttributeLibraryPath ());
cargs.AddRange (refs);
cargs.Add ("-r:" + baselibdll);
foreach (var def in defines)
cargs.Add ("-define:" + def);
#if NET
cargs.Add ("-define:NET");
#endif
cargs.AddRange (paths);
if (nostdlib) {
cargs.Add ("-nostdlib");
cargs.Add ("-noconfig");
}
cargs.AddRange (api_sources);
cargs.AddRange (core_sources);
if (!string.IsNullOrEmpty (Path.GetDirectoryName (baselibdll)))
cargs.Add ("-lib:" + Path.GetDirectoryName (baselibdll));

AddNFloatUsing (cargs, tmpdir);

Compile (cargs, 2);

return tmpass;
}

void AddNFloatUsing (List<string> cargs, string tmpdir)
{
#if NET
if (noNFloatUsing)
return;
var tmpusing = Path.Combine (tmpdir, "GlobalUsings.g.cs");
File.WriteAllText (tmpusing, "global using nfloat = global::System.Runtime.InteropServices.NFloat;\n");
cargs.Add (tmpusing);
#endif
}

void Compile (List<string> arguments, int errorCode)
{
if (compile_command is null || compile_command.Length == 0) {
Expand Down

6 comments on commit a8ba9c4

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.