diff options
author | Danny Milosavljevic <dannym@friendly-machines.com> | 2025-06-10 16:56:31 +0200 |
---|---|---|
committer | Sharlatan Hellseher <sharlatanus@gmail.com> | 2025-06-23 23:05:27 +0100 |
commit | 4f10384b546bb93b7dd06ae3b99a60c7c15f1d59 (patch) | |
tree | 40f451ba029df11e552bd20c61ae9164b3c0dc29 | |
parent | d0ba40a7a5a01c0bbe33196ee0be1cc23ca2d176 (diff) |
gnu: mono@1.9.1: Make it reproducible.
* gnu/packages/patches/mono-1.9.1-reproducibility.patch: New file.
* gnu/local.mk (dist_patch_DATA): Register it.
* gnu/packages/dotnet.scm (mono-1.9.1)[source]: Add it.
[arguments]<#:make-flags>: Add NO_SIGN_ASSEMBLY.
<#:phases>[delete-mdb]: New phase.
[disable-signing]: New phase.
Change-Id: I094692a1aa74d7737fa781e88582e8a0a3a27dbb
Reviewed-by: Ludovic Courtès <ludo@gnu.org>
Signed-off-by: Sharlatan Hellseher <sharlatanus@gmail.com>
-rw-r--r-- | gnu/local.mk | 1 | ||||
-rw-r--r-- | gnu/packages/dotnet.scm | 25 | ||||
-rw-r--r-- | gnu/packages/patches/mono-1.9.1-reproducibility.patch | 216 |
3 files changed, 240 insertions, 2 deletions
diff --git a/gnu/local.mk b/gnu/local.mk index 1e2299bca9..af4fe6d9ff 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1890,6 +1890,7 @@ dist_patch_DATA = \ %D%/packages/patches/mono-1.2.6-bootstrap.patch \ %D%/packages/patches/mono-1.9.1-add-MONO_CREATE_IMAGE_VERSION.patch \ %D%/packages/patches/mono-1.9.1-fixes.patch \ + %D%/packages/patches/mono-1.9.1-reproducibility.patch \ %D%/packages/patches/mono-2.4.2.3-fixes.patch \ %D%/packages/patches/mono-2.6.4-fixes.patch \ %D%/packages/patches/mono-2.11.4-fixes.patch \ diff --git a/gnu/packages/dotnet.scm b/gnu/packages/dotnet.scm index 2ad9a17b45..3ee9cab76f 100644 --- a/gnu/packages/dotnet.scm +++ b/gnu/packages/dotnet.scm @@ -419,7 +419,8 @@ a C-style programming language from Microsoft that is very similar to Java.") (snippet prepare-mono-source) (patches (search-patches "mono-1.9.1-fixes.patch" - "mono-1.9.1-add-MONO_CREATE_IMAGE_VERSION.patch")))) + "mono-1.9.1-add-MONO_CREATE_IMAGE_VERSION.patch" + "mono-1.9.1-reproducibility.patch")))) (native-inputs (modify-inputs (package-native-inputs mono-1.2.6) (delete "pnet-git") @@ -431,9 +432,29 @@ a C-style programming language from Microsoft that is very similar to Java.") (arguments (substitute-keyword-arguments (package-arguments mono-1.2.6) ((#:make-flags _ #f) - #~(list #$(string-append "CC=" (cc-for-target)) "V=1")) + #~(list #$(string-append "CC=" (cc-for-target)) + "NO_SIGN_ASSEMBLY=yes" ; non-reproducible otherwise. + "V=1")) ((#:phases phases #~%standard-phases) #~(modify-phases #$phases + (add-before 'install 'delete-mdb + (lambda _ + ;; Those are a source of non-reproducibility--because of the + ;; random GUIDs. We are also nerfing the module GUIDs anyway + ;; so I don't think .net still knows which mdb module is for + ;; what implementation module. + (for-each delete-file (find-files "." "[.]mdb$")))) + ;; Note: Would also work directly after unpack. + (add-after 'configure 'disable-signing + (lambda _ + ;; This would be a source of non-reproducibility and have no /keyfile. + (substitute* "mcs/class/IBM.Data.DB2/Makefile" + (("^LIB_MCS_FLAGS =") + "LIB_MCS_FLAGS = /delaysign+ ")) + ;; This would be a source of non-reproducibility. + (substitute* "mcs/class/FirebirdSql.Data.Firebird/Assembly/AssemblyInfo.cs" + (("AssemblyDelaySign[(]false[)]") + "AssemblyDelaySign(true)")))) (add-before 'configure 'set-cflags (lambda _ ;; apparently can't be set via make flags in this version diff --git a/gnu/packages/patches/mono-1.9.1-reproducibility.patch b/gnu/packages/patches/mono-1.9.1-reproducibility.patch new file mode 100644 index 0000000000..db6ea5f419 --- /dev/null +++ b/gnu/packages/patches/mono-1.9.1-reproducibility.patch @@ -0,0 +1,216 @@ +Author: Danny Milosavljevic <dannym@friendly-machines.com> +Date: 10 Jun 2025 +Subject: Fix sources of non-reproducibility. + +diff -ru orig/mono-1.9.1-checkout/mcs/class/corlib/System.Reflection.Emit/ModuleBuilder.cs mono-1.9.1-checkout/mcs/class/corlib/System.Reflection.Emit/ModuleBuilder.cs +--- orig/mono-1.9.1-checkout/mcs/class/corlib/System.Reflection.Emit/ModuleBuilder.cs 2025-06-09 11:58:58.679365113 +0200 ++++ mono-1.9.1-checkout/mcs/class/corlib/System.Reflection.Emit/ModuleBuilder.cs 2025-06-09 19:10:46.839764717 +0200 +@@ -80,7 +80,7 @@ + this.assembly = this.assemblyb = assb; + this.transient = transient; + // to keep mcs fast we do not want CryptoConfig wo be involved to create the RNG +- guid = Guid.FastNewGuidArray (); ++ guid = new byte[16]; // = Guid.Empty.ToByteArray(); + // guid = Guid.NewGuid().ToByteArray (); + table_idx = get_next_table_index (this, 0x00, true); + name_cache = new Hashtable (); +diff -ru orig/mono-1.9.1-checkout/mcs/class/Mono.Cecil/Mono.Cecil.Binary/ImageInitializer.cs mono-1.9.1-checkout/mcs/class/Mono.Cecil/Mono.Cecil.Binary/ImageInitializer.cs +--- orig/mono-1.9.1-checkout/mcs/class/Mono.Cecil/Mono.Cecil.Binary/ImageInitializer.cs 2025-06-09 11:58:58.233978153 +0200 ++++ mono-1.9.1-checkout/mcs/class/Mono.Cecil/Mono.Cecil.Binary/ImageInitializer.cs 2025-06-09 16:46:46.086454131 +0200 +@@ -132,6 +132,15 @@ + + public static uint TimeDateStampFromEpoch () + { ++ string sourceDateEpoch = Environment.GetEnvironmentVariable("SOURCE_DATE_EPOCH"); ++ if (sourceDateEpoch != null && sourceDateEpoch != "") { ++ try { ++ return uint.Parse(sourceDateEpoch); ++ } catch { ++ // fallthrough ++ } ++ } ++ + return (uint) DateTime.UtcNow.Subtract ( + new DateTime (1970, 1, 1)).TotalSeconds; + } +diff -ru orig/mono-1.9.1-checkout/mcs/mcs/anonymous.cs mono-1.9.1-checkout/mcs/mcs/anonymous.cs +--- orig/mono-1.9.1-checkout/mcs/mcs/anonymous.cs 2025-06-09 11:58:58.814338639 +0200 ++++ mono-1.9.1-checkout/mcs/mcs/anonymous.cs 2025-06-09 22:27:26.049258977 +0200 +@@ -21,6 +21,7 @@ + + namespace Mono.CSharp { + ++ + public abstract class CompilerGeneratedClass : Class + { + GenericMethod generic_method; +@@ -174,6 +175,61 @@ + throw new InternalErrorException ("Helper class already defined!"); + } + ++// ++// A robust, standalone, and deterministic comparer for all types that ++// inherit from the abstract class 'Variable'. This version uses only ++// C# 2.0 compatible syntax. ++// ++public class VariableComparer : System.Collections.IComparer ++{ ++ // Helper method to safely get a comparable name from any Variable type. ++ private string GetVariableName(object obj) ++ { ++ // Case 1: The object is a 'CapturedVariable' or any of its children. ++ if (obj is ScopeInfo.CapturedVariable) ++ { ++ // Explicit cast required for C# 2.0 ++ ScopeInfo.CapturedVariable cv = (ScopeInfo.CapturedVariable)obj; ++ return cv.Name; ++ } ++ ++ // Case 2: The object is a 'LocalVariable' from statement.cs. ++ if (obj is LocalInfo.LocalVariable) ++ { ++ // Explicit cast required for C# 2.0 ++ LocalInfo.LocalVariable lv = (LocalInfo.LocalVariable)obj; ++ return lv.LocalInfo.Name; ++ } ++ ++ // ++ // Fallback for any other unknown 'Variable' subtype. ++ // ++ return obj.GetType().FullName; ++ } ++ ++ // The single method required by the IComparer interface. ++ public int Compare(object x, object y) ++ { ++ // Handle nulls gracefully. ++ if (x == null && y == null) return 0; ++ if (x == null) return -1; ++ if (y == null) return 1; ++ ++ string name_x = GetVariableName(x); ++ string name_y = GetVariableName(y); ++ ++ // 1. Primary Sort Key: The extracted variable name. ++ int name_compare = string.CompareOrdinal(name_x, name_y); ++ if (name_compare != 0) ++ { ++ return name_compare; ++ } ++ ++ // 2. Secondary Sort Key (Stable Tie-breaker): The full type name. ++ return string.CompareOrdinal(x.GetType().FullName, y.GetType().FullName); ++ } ++} ++ + protected class CapturedVariableField : Field + { + public CapturedVariableField (CompilerGeneratedClass helper, string name, +@@ -264,9 +320,11 @@ + + protected CapturedScope[] CapturedScopes { + get { +- CapturedScope[] list = new CapturedScope [captured_scopes.Count]; +- captured_scopes.Values.CopyTo (list, 0); +- return list; ++ ArrayList list = new ArrayList(captured_scopes.Values); ++ list.Sort(new VariableComparer()); ++ CapturedScope[] result = new CapturedScope[list.Count]; ++ list.CopyTo(result, 0); ++ return result; + } + } + +@@ -420,7 +478,7 @@ + return new ScopeInitializer (this); + } + +- protected abstract class CapturedVariable : Variable ++ public abstract class CapturedVariable : Variable + { + public readonly ScopeInfo Scope; + public readonly string Name; +@@ -493,7 +551,7 @@ + } + } + +- protected class CapturedParameter : CapturedVariable { ++ public class CapturedParameter : CapturedVariable { + public readonly Parameter Parameter; + public readonly int Idx; + +@@ -511,7 +569,7 @@ + } + } + +- protected class CapturedLocal : CapturedVariable { ++ public class CapturedLocal : CapturedVariable { + public readonly LocalInfo Local; + + public CapturedLocal (ScopeInfo scope, LocalInfo local) +@@ -527,7 +585,7 @@ + } + } + +- protected class CapturedThis : CapturedVariable { ++ public class CapturedThis : CapturedVariable { + public CapturedThis (RootScopeInfo host) + : base (host, "<>THIS", host.ParentType) + { } +@@ -646,7 +704,9 @@ + } else + scope_instance = ec.ig.DeclareLocal (type); + +- foreach (CapturedLocal local in Scope.locals.Values) { ++ ArrayList sorted_locals = new ArrayList(Scope.locals.Values); ++ sorted_locals.Sort(new VariableComparer()); ++ foreach (CapturedLocal local in sorted_locals) { + FieldExpr fe = (FieldExpr) Expression.MemberLookup ( + ec.ContainerType, type, local.Field.Name, loc); + Report.Debug (64, "RESOLVE SCOPE INITIALIZER #2", this, Scope, +@@ -660,7 +720,9 @@ + } + + if (Scope.HostsParameters) { +- foreach (CapturedParameter cp in Scope.captured_params.Values) { ++ ArrayList sorted_params = new ArrayList(Scope.captured_params.Values); ++ sorted_params.Sort(new VariableComparer()); ++ foreach (CapturedParameter cp in sorted_params) { + FieldExpr fe = (FieldExpr) Expression.MemberLookup ( + ec.ContainerType, type, cp.Field.Name, loc); + if (fe == null) +@@ -775,7 +837,9 @@ + captured_scope.EmitAssign (ec); + + if (Scope.HostsParameters) { +- foreach (CapturedParameter cp in Scope.captured_params.Values) { ++ ArrayList sorted_params = new ArrayList(Scope.captured_params.Values); ++ sorted_params.Sort(new VariableComparer()); ++ foreach (CapturedParameter cp in sorted_params) { + Report.Debug (128, "EMIT SCOPE INIT #6", this, + ec, ec.IsStatic, Scope, cp, cp.Field.Name); + DoEmitInstance (ec); +diff -ru orig/mono-1.9.1-checkout/mcs/mcs/statement.cs mono-1.9.1-checkout/mcs/mcs/statement.cs +--- orig/mono-1.9.1-checkout/mcs/mcs/statement.cs 2025-06-09 11:58:58.816851529 +0200 ++++ mono-1.9.1-checkout/mcs/mcs/statement.cs 2025-06-09 22:07:10.441563853 +0200 +@@ -1392,7 +1392,7 @@ + get { return Location; } + } + +- protected class LocalVariable : Variable ++ public class LocalVariable : Variable + { + public readonly LocalInfo LocalInfo; + LocalBuilder builder; +diff -ru orig/mono-1.9.1-checkout/mono/metadata/reflection.c mono-1.9.1-checkout/mono/metadata/reflection.c +--- orig/mono-1.9.1-checkout/mono/metadata/reflection.c 2025-06-09 11:58:58.903462701 +0200 ++++ mono-1.9.1-checkout/mono/metadata/reflection.c 2025-06-09 18:44:58.063693593 +0200 +@@ -4851,7 +4851,7 @@ + + header->coff.coff_machine = GUINT16_FROM_LE (assemblyb->machine); + header->coff.coff_sections = GUINT16_FROM_LE (nsections); +- header->coff.coff_time = GUINT32_FROM_LE (time (NULL)); ++ header->coff.coff_time = GUINT32_FROM_LE (getenv("SOURCE_DATE_EPOCH") ? atoi(getenv("SOURCE_DATE_EPOCH")) : time (NULL)); + header->coff.coff_opt_header_size = GUINT16_FROM_LE (sizeof (MonoDotNetHeader) - sizeof (MonoCOFFHeader) - 4); + if (assemblyb->pekind == 1) { + /* it's a dll */ |