summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Milosavljevic <dannym@friendly-machines.com>2025-06-10 16:56:31 +0200
committerSharlatan Hellseher <sharlatanus@gmail.com>2025-06-23 23:05:27 +0100
commit4f10384b546bb93b7dd06ae3b99a60c7c15f1d59 (patch)
tree40f451ba029df11e552bd20c61ae9164b3c0dc29
parentd0ba40a7a5a01c0bbe33196ee0be1cc23ca2d176 (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.mk1
-rw-r--r--gnu/packages/dotnet.scm25
-rw-r--r--gnu/packages/patches/mono-1.9.1-reproducibility.patch216
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 */