Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"csharp.unitTestDebuggingOptions": {
// It would be preferable to "fix" the test projects to target an executable .net core framework (https://stackoverflow.com/a/48885500/2301416)
"type": "clr" // https://github.com/OmniSharp/omnisharp-vscode/wiki/Desktop-.NET-Framework#settingsjson-example
"dotnet.unitTestDebuggingOptions": {
"type": "clr"
}
}
127 changes: 127 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# AGENTS: liblcm (LCM)

## Summary
liblcm (LCM) is the core FieldWorks Language & Culture Model library for linguistic analyses. It provides the data model, serialization, utilities, and tooling for linguistic, anthropological, and text corpus data. It is a multi-project .NET solution with code generation steps and multi-targeting for legacy .NET Framework and modern .NET.

## High-level repo facts
- Type: .NET solution (multi-project class libraries + build tasks + tools + tests).
- Languages: C# (.cs), MSBuild (.proj/.csproj/.props/.targets), XML, shell/batch scripts.
- Target frameworks: net462, netstandard2.0, net8.0 (see .csproj files in src/ and tests/).
- Build tools: MSBuild, dotnet SDK, GitVersion.MsBuild, NUnit.
- Output: artifacts/ (NuGet packages and binaries by configuration/TFM).

## Build and validation (validated commands and observations)

### What CI runs (GitHub Actions)
CI runs on Windows and Ubuntu. See .github/workflows/ci-cd.yml:
1) Install .NET SDK 8.x.
2) Ubuntu: install mono-devel and icu-fw packages.
3) Windows: remove c:\tools\php\icuuc*.dll; install .NET Framework 4.6.1 targeting pack.
4) Build: dotnet build --configuration Release
5) Test:
- Linux: . environ && dotnet test --no-restore --no-build -p:ParallelizeAssembly=false --configuration Release
- Windows: dotnet test --no-restore --no-build -p:ParallelizeAssembly=false --configuration Release
6) Pack: dotnet pack --include-symbols --no-restore --no-build -p:SymbolPackageFormat=snupkg --configuration Release

Always mirror this sequence when validating a change locally.

### Local build scripts (not validated here)
- Windows: build.cmd [Debug|Release] [Target] (uses MSBuild on LCM.sln).
- Linux: build.sh [Debug|Release] [Target] (sources environ, uses msbuild on LCM.sln).
These scripts call build/LCM.proj targets (Build/Test/Pack). If you use them, always run from repo root.

### Tests per README (not validated here)
- Windows, ReSharper: open LCM.sln and “Run Unit Tests”.
- Windows, no ReSharper: use MSBuild, then run nunit3-console.exe from artifacts/Debug/net462.
- Linux terminal: source environ, then run mono with nunit3-console.exe on *Tests.dll in artifacts/Debug/net462.

### Commands actually run during onboarding
- dotnet test .\LCM.sln → FAILED
- dotnet build --configuration Release → FAILED
Failure signature (both commands): GitVersion.MsBuild (netcoreapp3.1 gitversion.dll) exited with code 1. This blocks build/test in this environment. CI uses fetch-depth 0, so ensure a full git history is available. If GitVersion still fails, check GitVersion prerequisites and local .NET runtime compatibility.

No command timeouts were observed.

### Known prerequisites and gotchas
- GitVersion.MsBuild is used across projects; it requires git metadata. CI checks out with fetch-depth 0.
- net462 builds on Windows require the .NET Framework 4.6.1 targeting pack (CI installs it).
- ICU data generation requires ICU binaries (CI installs icu-fw on Ubuntu).
- Some projects warn on NU1701; treat as warnings unless build breaks.
- The build prohibits references to System.Windows.Forms (CheckWinForms target).

## Project layout and architecture

### Key solution and build files
- LCM.sln: solution entry point.
- build.cmd / build.sh: wrapper scripts for MSBuild.
- build/LCM.proj: orchestrated build/test/pack, uses NUnit console on output/ for legacy builds.
- Directory.Build.props / Directory.Build.targets: repo-wide build settings and packaging.
- Directory.Solution.props / Directory.Solution.targets: solution-level defaults.
- GitVersion.yml: GitVersion configuration.
- global.json: SDK roll-forward config.
- .editorconfig: formatting rules.

### Major source projects (src/)
- src/SIL.LCModel: main LCM library (net462; netstandard2.0).
- src/SIL.LCModel.Core: core utilities and ICU data generation (netstandard2.0; net462; net8.0).
- src/SIL.LCModel.Utils: shared utilities (net462; netstandard2.0).
- src/SIL.LCModel.Build.Tasks: MSBuild tasks used for code generation.
- src/SIL.LCModel.FixData: data-fix utilities.
- src/CSTools: auxiliary tools (pg/lg/Tools).

Code generation targets to know about:
- SIL.LCModel: GenerateModel (MasterLCModel.xml → Generated*.cs).
- SIL.LCModel.Core: GenerateKernelCs, GenerateIcuData.

### Tests (tests/)
- SIL.LCModel.Tests
- SIL.LCModel.Core.Tests
- SIL.LCModel.Utils.Tests
- SIL.LCModel.FixData.Tests
- TestHelper (support project)

### CI/validation checks
- GitHub Actions: .github/workflows/ci-cd.yml (build, test, pack, publish).
- Tests run with dotnet test and ParallelizeAssembly=false.
- Packaging uses dotnet pack with symbol packages.

### Dependencies not obvious from layout
- ICU data and binaries (icu-fw) for Core ICU generation.
- Mono on Linux for some runtime/test workflows.
- GitVersion.MsBuild for versioning (requires git metadata).

## Root files list
- .editorconfig
- .gitattributes
- .gitignore
- build.cmd
- build.sh
- CHANGELOG.md
- Directory.Build.props
- Directory.Build.targets
- Directory.Solution.props
- Directory.Solution.targets
- environ
- GitVersion.yml
- global.json
- LCM.sln
- LCM.sln.DotSettings
- LICENSE
- README.md

## Repo top-level directories
- .github/ (GitHub Actions workflow)
- .vscode/ (VS settings)
- artifacts/ (build outputs)
- build/ (LCM.proj)
- src/ (production code)
- tests/ (unit tests)

## README highlights (summary)
- Describes liblcm as FieldWorks model library for linguistic analyses.
- Build: use build.cmd (Windows) or build.sh (Linux). Default Debug, optional Release.
- Debugging: use LOCAL_NUGET_REPO to publish local packages; see NuGet local feeds.
- Tests: Windows via ReSharper or NUnit console; Linux via mono + NUnit console (requires environ).

## Trust these instructions
Follow this file first. Only search the repo if these instructions are incomplete or prove incorrect for your task.
6 changes: 6 additions & 0 deletions src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ void SaveOnIdle(object sender, ElapsedEventArgs e)
// Check if we are already in SaveInternal.
if (m_fInSaveInternal)
return;
// Check if we are disposed before accessing m_ui
if (IsDisposed)
return;
if (m_ui == null)
return;

// Don't save if we're in the middle of something and not in the right state to Save!
if (UndoOrRedoInProgress || CurrentProcessingState != BusinessTransactionState.ReadyForBeginTask)
return; // don't start another, if for example the conflict dialog is open.
Expand Down
28 changes: 27 additions & 1 deletion tests/SIL.LCModel.Core.Tests/Text/CustomIcuFallbackTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
#if NET40
typeof(CustomIcuFallbackTests).Assembly.CodeBase
#else
typeof(CustomIcuFallbackTests).GetTypeInfo().Assembly.CodeBase

Check warning on line 54 in tests/SIL.LCModel.Core.Tests/Text/CustomIcuFallbackTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

'Assembly.CodeBase' is obsolete: 'Assembly.CodeBase and Assembly.EscapedCodeBase are only included for .NET Framework compatibility. Use Assembly.Location.' (https://aka.ms/dotnet-warnings/SYSLIB0012)

Check warning on line 54 in tests/SIL.LCModel.Core.Tests/Text/CustomIcuFallbackTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-22.04)

'Assembly.CodeBase' is obsolete: 'Assembly.CodeBase and Assembly.EscapedCodeBase are only included for .NET Framework compatibility. Use Assembly.Location.' (https://aka.ms/dotnet-warnings/SYSLIB0012)
#endif
)
.LocalPath);
Expand Down Expand Up @@ -164,7 +164,8 @@
public void FixtureSetUp()
{
// Undo the PATH that got set by the InitializeIcu attribute
Environment.SetEnvironmentVariable("PATH", InitializeIcuAttribute.PreTestPathEnvironment);
var originalPath = InitializeIcuAttribute.PreTestPathEnvironment;
Environment.SetEnvironmentVariable("PATH", RemoveIcuPaths(originalPath));
_dirsToDelete = new List<string>();
_preTestDataDir = Wrapper.DataDirectory;
_preTestDataDirEnv = Environment.GetEnvironmentVariable("ICU_DATA");
Expand Down Expand Up @@ -237,6 +238,31 @@
}
}

private static string RemoveIcuPaths(string path)
{
if (string.IsNullOrEmpty(path))
return path;

var filtered = new List<string>();
foreach (var folder in path.Split(Path.PathSeparator))
{
if (string.IsNullOrWhiteSpace(folder))
continue;
try
{
if (Directory.Exists(folder) && Directory.EnumerateFiles(folder, "icuuc*.dll").Any())
continue;
}
catch
{
// If we can't enumerate the folder, keep it to avoid breaking PATH unexpectedly.
}
filtered.Add(folder);
}

return string.Join(Path.PathSeparator.ToString(), filtered);
}

[Test]
public void InitIcuDataDir_NoIcuLibrary()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2026 SIL International
// This software is licensed under the LGPL, version 2.1 or later
// (http://www.gnu.org/licenses/lgpl-2.1.html)

using System;
using System.Reflection;
using NUnit.Framework;
using SIL.LCModel;
using SIL.LCModel.Core.KernelInterfaces;

namespace SIL.LCModel.Infrastructure.Impl
{
[TestFixture]
public class UnitOfWorkServiceTests : MemoryOnlyBackendProviderTestBase
{
[Test]
public void SaveOnIdle_UiCleared_DoesNotThrow()
{
var uowService = Cache.ServiceLocator.GetInstance<IUnitOfWorkService>();
var serviceInstance = (object)uowService;

InvokeNonPublicVoid(serviceInstance, "StopSaveTimer");
SetNonPublicField(serviceInstance, "m_ui", null);

Assert.DoesNotThrow(() =>
InvokeNonPublicVoid(serviceInstance, "SaveOnIdle", null, null));
}

private static void SetNonPublicField(object instance, string fieldName, object value)
{
var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
Assert.Fail("Field not found: " + fieldName);
field.SetValue(instance, value);
}

private static void InvokeNonPublicVoid(object instance, string methodName, params object[] args)
{
var method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (method == null)
Assert.Fail("Method not found: " + methodName);
method.Invoke(instance, args);
}
}
}
Loading