diff --git a/.github/workflows/ksp-snapshot-integration.yml b/.github/workflows/ksp-snapshot-integration.yml index 160674e1e6796..e11890ad23c85 100644 --- a/.github/workflows/ksp-snapshot-integration.yml +++ b/.github/workflows/ksp-snapshot-integration.yml @@ -69,7 +69,7 @@ jobs: # Gradle flags match those used in presubmit.yml, plus: # * disabling validating integration patches as a patch file may already be applied # * disabling klibs cross compilation because it is not supported with cinterops - # * disabling non-Jvm tests to avoid memory exhaustion + # * setting max workers to 2 to avoid memory exhaustion gradle-flags: > --max-workers=2 -Pkotlin.native.enableKlibsCrossCompilation=false @@ -81,8 +81,5 @@ jobs: --stacktrace -x validateIntegrationPatches -x checkKotlinApiTarget - -x linuxX64Test - -x wasmJsBrowserTest - -x jsBrowserTest # Disable the cache since this is the only build using the latest KSP. gradle-cache-disabled: true diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/FrameGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/FrameGraph.kt index fdad3a60d64da..8205d383dab7e 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/FrameGraph.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/FrameGraph.kt @@ -59,7 +59,7 @@ public interface FrameGraph : CameraGraphBase, CameraControl public fun captureWith( streamIds: Set = emptySet(), parameters: Map = emptyMap(), - capacity: Int = 1, + capacity: Int = DEFAULT_FRAME_BUFFER_CAPACITY, ): FrameBuffer /** @@ -74,4 +74,16 @@ public interface FrameGraph : CameraGraphBase, CameraControl * Example: A [Session] should *not* be held during video recording. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public interface Session : CameraGraph.Session + + public companion object { + private const val DEFAULT_FRAME_BUFFER_CAPACITY = 1 + + /** Utility function for the common case of attaching a single stream. See [captureWith]. */ + @JvmStatic + public fun FrameGraph.captureWith( + streamId: StreamId, + parameters: Map = emptyMap(), + capacity: Int = DEFAULT_FRAME_BUFFER_CAPACITY, + ): FrameBuffer = captureWith(setOf(streamId), parameters, capacity) + } } diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt index ae7bfdebb73ea..c0ecdfebc7df2 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Frames.kt @@ -27,7 +27,9 @@ import androidx.annotation.RestrictTo */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @JvmInline -public value class FrameNumber(public val value: Long) +public value class FrameNumber(public val value: Long) { + override fun toString(): String = "Frame-$value" +} /** [FrameInfo] is a wrapper around [TotalCaptureResult]. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt index dd7b4cda078df..26debff4cc184 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/Controller3A.kt @@ -770,8 +770,10 @@ internal class Controller3A( } debug { - "lock3AForCapture result: meetsAeCondition = $meetsAeCondition" + - ", meetsAfCondition = $meetsAfCondition, meetsAwbCondition = $meetsAwbCondition" + "lock3AForCapture state ${frameMetadata.frameNumber}: " + + "meetsAeCondition = $meetsAeCondition, " + + "meetsAfCondition = $meetsAfCondition, " + + "meetsAwbCondition = $meetsAwbCondition" } meetsAeCondition && meetsAfCondition && meetsAwbCondition diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt index ca569fbc0b651..a076c1c9ac299 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/FrameState.kt @@ -78,21 +78,28 @@ internal class FrameState( private val state = atomic(STARTED) private val streamResultCount = atomic(0) - private val outputFrameListeners = CopyOnWriteArrayList() + // A list of ListenerState, one for each listener. + private val listenerStates = CopyOnWriteArrayList() fun addListener(listener: Frame.Listener) { - listener.onFrameStarted(frameNumber, frameTimestamp) - - // Note: This operation is safe since the outputFrameListeners is a CopyOnWriteArrayList. - outputFrameListeners.add(listener) + val listenerState = ListenerState(listener) + listenerStates.add(listenerState) + + val currentFrameState = state.value + + // Listeners can be added during any Frame state. We want to trigger the callbacks that were + // already triggered before the listener is added. + when (currentFrameState) { + STARTED -> listenerState.invokeOnStarted(frameNumber, frameTimestamp) + FRAME_INFO_COMPLETE -> + listenerState.invokeOnFrameInfoAvailable(frameNumber, frameTimestamp) + STREAM_RESULTS_COMPLETE -> + listenerState.invokeOnImagesAvailable(frameNumber, frameTimestamp) + COMPLETE -> listenerState.invokeOnFrameComplete(frameNumber, frameTimestamp) + } } fun onFrameInfoComplete() { - // Invoke the onOutputResultsAvailable onOutputMetadataAvailable. - for (i in outputFrameListeners.indices) { - outputFrameListeners[i].onFrameInfoAvailable() - } - val state = state.updateAndGet { current -> when (current) { @@ -105,25 +112,23 @@ internal class FrameState( } } + for (listenerState in listenerStates) { + listenerState.invokeOnFrameInfoAvailable(frameNumber, frameTimestamp) + } + if (state == COMPLETE) { invokeOnFrameComplete() } } fun onStreamResultComplete(streamId: StreamId) { - val allResultsCompleted = streamResultCount.incrementAndGet() != imageOutputs.size + val hasResultsRemaining = streamResultCount.incrementAndGet() != imageOutputs.size - // Invoke the onOutputResultsAvailable listener. - for (i in outputFrameListeners.indices) { - outputFrameListeners[i].onImageAvailable(streamId) + for (listenerState in listenerStates) { + listenerState.invokeOnImageAvailable(streamId) } - if (allResultsCompleted) return - - // Invoke the onOutputResultsAvailable listener. - for (i in outputFrameListeners.indices) { - outputFrameListeners[i].onImagesAvailable() - } + if (hasResultsRemaining) return val state = state.updateAndGet { current -> @@ -137,15 +142,18 @@ internal class FrameState( } } + for (listenerState in listenerStates) { + listenerState.invokeOnImagesAvailable(frameNumber, frameTimestamp) + } + if (state == COMPLETE) { invokeOnFrameComplete() } } private fun invokeOnFrameComplete() { - // Invoke the onOutputResultsAvailable listener. - for (i in outputFrameListeners.indices) { - outputFrameListeners[i].onFrameComplete() + for (listenerState in listenerStates) { + listenerState.invokeOnFrameComplete(frameNumber, frameTimestamp) } } diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/ListenerState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/ListenerState.kt index 55458fa9236c9..585a9366b247e 100644 --- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/ListenerState.kt +++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/ListenerState.kt @@ -19,6 +19,7 @@ package androidx.camera.camera2.pipe.internal import androidx.camera.camera2.pipe.CameraTimestamp import androidx.camera.camera2.pipe.Frame import androidx.camera.camera2.pipe.FrameNumber +import androidx.camera.camera2.pipe.StreamId import kotlinx.atomicfu.atomic internal class ListenerState(val listener: Frame.Listener) { @@ -83,4 +84,13 @@ internal class ListenerState(val listener: Frame.Listener) { listener.onFrameComplete() } } + + /** + * Invokes [listener.onImageAvailable(streamId)]. + * + * @param streamId The [StreamId] that the image is available + */ + fun invokeOnImageAvailable(streamId: StreamId) { + listener.onImageAvailable(streamId) + } } diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameStateTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameStateTest.kt index cf3ac579430d4..6213d773d9e3b 100644 --- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameStateTest.kt +++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/internal/FrameStateTest.kt @@ -17,6 +17,7 @@ package androidx.camera.camera2.pipe.internal import androidx.camera.camera2.pipe.CameraTimestamp +import androidx.camera.camera2.pipe.Frame import androidx.camera.camera2.pipe.FrameNumber import androidx.camera.camera2.pipe.OutputId import androidx.camera.camera2.pipe.OutputStatus @@ -29,6 +30,11 @@ import androidx.camera.camera2.pipe.testing.FakeImage import androidx.camera.camera2.pipe.testing.FakeRequestMetadata import androidx.camera.camera2.pipe.testing.FakeSurfaces import com.google.common.truth.Truth.assertThat +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Test import org.junit.runner.RunWith @@ -63,6 +69,35 @@ class FrameStateTest { private val fakeFrameInfo = FakeFrameInfo(metadata = fakeFrameMetadata, requestMetadata = fakeRequestMetadata) + private val fakeListener = + object : Frame.Listener { + val frameStartedCalled = atomic(0) + val frameInfoAvailableCalled = atomic(0) + val imageAvailableCalled = atomic(0) + val frameCompletedCalled = atomic(0) + + override fun onFrameStarted(frameNumber: FrameNumber, frameTimestamp: CameraTimestamp) { + frameStartedCalled.incrementAndGet() + } + + override fun onFrameInfoAvailable() { + frameInfoAvailableCalled.incrementAndGet() + } + + override fun onImageAvailable(streamId: StreamId) { + // Do nothing. ListenerState doesn't care about onImageAvailable on stream level + // currently. + } + + override fun onImagesAvailable() { + imageAvailableCalled.incrementAndGet() + } + + override fun onFrameComplete() { + frameCompletedCalled.incrementAndGet() + } + } + private val frameState = FrameState( requestMetadata = fakeRequestMetadata, @@ -207,4 +242,125 @@ class FrameStateTest { assertThat(frameState.frameInfoOutput.status).isEqualTo(OutputStatus.UNAVAILABLE) assertThat(frameState.frameInfoOutput.outputOrNull()).isNull() } + + @Test + fun addListener_invokesOnStarted_whenStateIsStarted() { + // FrameState's initial state is STARTED + frameState.addListener(fakeListener) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun addListener_stateIsFrameInfoAvailable_invokesStartAndFrameInfoComplete() { + frameState.onFrameInfoComplete() + + frameState.addListener(fakeListener) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun addListener_stateIsImagesAvailable_invokesStartAndImagesAvailable() { + // All stream result completed + frameState.onStreamResultComplete(stream1Id) + frameState.onStreamResultComplete(stream2Id) + + frameState.addListener(fakeListener) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun addListener_stateIsFrameComplete_invokesAllCallbacks() { + frameState.onStreamResultComplete(stream1Id) + frameState.onStreamResultComplete(stream2Id) + frameState.onFrameInfoComplete() + + frameState.addListener(fakeListener) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(1) + } + + @Test + fun onFrameInfoComplete_invokesOnFrameInfoAvailable() { + frameState.addListener(fakeListener) + + frameState.onFrameInfoComplete() + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun onStreamResultComplete_doesNotHaveStreamResultForAllStreams_doesNotInvokesOnImagesAvailable() { + frameState.addListener(fakeListener) + + frameState.onStreamResultComplete(stream1Id) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun onStreamResultComplete_invokesOnImagesAvailable_afterAllStreamsComplete() { + frameState.addListener(fakeListener) + + frameState.onStreamResultComplete(stream1Id) + frameState.onStreamResultComplete(stream2Id) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(0) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(0) + } + + @Test + fun frameState_transitionsToComplete_allCallbacksAreTriggered() { + frameState.addListener(fakeListener) + + frameState.onFrameInfoComplete() + frameState.onStreamResultComplete(stream1Id) + frameState.onStreamResultComplete(stream2Id) + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(1) + } + + @Test + fun concurrentFrameStateChangeAndNewListenerAdded_ensureCallbacksCalledOnce() = runBlocking { + val numCoroutines = 4 + + val jobs = + listOf( + launch(Dispatchers.Default) { frameState.onFrameInfoComplete() }, + launch(Dispatchers.Default) { frameState.onStreamResultComplete(stream1Id) }, + launch(Dispatchers.Default) { frameState.addListener(fakeListener) }, + launch(Dispatchers.Default) { frameState.onStreamResultComplete(stream2Id) }, + ) + jobs.joinAll() + + assertThat(fakeListener.frameStartedCalled.value).isEqualTo(1) + assertThat(fakeListener.frameInfoAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.imageAvailableCalled.value).isEqualTo(1) + assertThat(fakeListener.frameCompletedCalled.value).isEqualTo(1) + } } diff --git a/development/project-creator/java-template/groupId/artifactId/src/main/java/groupId/package-info.java b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Anchor.kt similarity index 80% rename from development/project-creator/java-template/groupId/artifactId/src/main/java/groupId/package-info.java rename to compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Anchor.kt index a51e68c20c760..be5b373850790 100644 --- a/development/project-creator/java-template/groupId/artifactId/src/main/java/groupId/package-info.java +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Anchor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) The Android Open Source Project + * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,8 @@ * limitations under the License. */ -/** - * Insert package level documentation here - */ -package ; +package androidx.compose.runtime + +internal interface Anchor { + val valid: Boolean +} diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt index 171f74da39778..3e4ef84c0d4d8 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt @@ -21,10 +21,10 @@ package androidx.compose.runtime import androidx.compose.runtime.Composer.Companion.Empty import androidx.compose.runtime.collection.ScopeMap -import androidx.compose.runtime.composer.gapbuffer.Anchor import androidx.compose.runtime.composer.gapbuffer.SlotReader import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter +import androidx.compose.runtime.composer.gapbuffer.asGapAnchor import androidx.compose.runtime.tooling.ComposeStackTrace import androidx.compose.runtime.tooling.ComposeStackTraceFrame import androidx.compose.runtime.tooling.ComposeStackTraceMode @@ -1365,7 +1365,7 @@ internal inline fun SlotWriter.withAfterAnchorInfo(anchor: Anchor?, cb: (Int var priority = -1 var endRelativeAfter = -1 if (anchor != null && anchor.valid) { - priority = anchorIndex(anchor) + priority = anchorIndex(anchor.asGapAnchor()) endRelativeAfter = slotsSize - slotsEndAllIndex(priority) } cb(priority, endRelativeAfter) @@ -1531,7 +1531,7 @@ internal fun extractMovableContentAtCurrent( if (anchor.valid) { val extracted = (composition as CompositionImpl).extractInvalidationsOfGroup { - slots.inGroup(anchor, it) + slots.inGroup(anchor.asGapAnchor(), it.asGapAnchor()) } reference.invalidations += extracted } @@ -1550,7 +1550,7 @@ internal fun extractMovableContentAtCurrent( writer.update(reference.parameter) // Move the content into current location - val anchors = slots.moveTo(reference.anchor, 1, writer) + val anchors = slots.moveTo(reference.anchor.asGapAnchor(), 1, writer) // skip the group that was just inserted. writer.skipGroup() diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt index df4c3307a6ea3..94700b538ef54 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt @@ -25,7 +25,6 @@ import androidx.collection.ScatterSet import androidx.compose.runtime.collection.ScopeMap import androidx.compose.runtime.collection.fastForEach import androidx.compose.runtime.composer.DebugStringFormattable -import androidx.compose.runtime.composer.gapbuffer.Anchor import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.asGapBufferSlotTable import androidx.compose.runtime.composer.gapbuffer.changelist.ChangeList @@ -38,7 +37,6 @@ import androidx.compose.runtime.snapshots.ReaderKind import androidx.compose.runtime.snapshots.StateObjectImpl import androidx.compose.runtime.snapshots.fastAll import androidx.compose.runtime.snapshots.fastAny -import androidx.compose.runtime.snapshots.fastForEach import androidx.compose.runtime.tooling.CompositionErrorContextImpl import androidx.compose.runtime.tooling.CompositionObserver import androidx.compose.runtime.tooling.CompositionObserverHandle diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/GapComposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/GapComposer.kt index 69ab0a80d56e5..e605f1a300d01 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/GapComposer.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/GapComposer.kt @@ -29,11 +29,12 @@ import androidx.compose.runtime.collection.MultiValueMap import androidx.compose.runtime.collection.ScopeMap import androidx.compose.runtime.composer.GroupInfo import androidx.compose.runtime.composer.GroupKind -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.KeyInfo import androidx.compose.runtime.composer.gapbuffer.SlotReader import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter +import androidx.compose.runtime.composer.gapbuffer.asGapAnchor import androidx.compose.runtime.composer.gapbuffer.asGapBufferSlotTable import androidx.compose.runtime.composer.gapbuffer.changelist.ChangeList import androidx.compose.runtime.composer.gapbuffer.changelist.ComposerChangeListWriter @@ -284,7 +285,7 @@ internal class GapComposer( override var deferredChanges: ChangeList? = null private val changeListWriter = ComposerChangeListWriter(this, changes) - private var insertAnchor: Anchor = insertTable.read { it.anchor(0) } + private var insertAnchor: GapAnchor = insertTable.read { it.anchor(0) } private var insertFixups = FixupList() private var pausable: Boolean = false @@ -1981,7 +1982,7 @@ internal class GapComposer( override fun tryImminentInvalidation(scope: RecomposeScopeImpl, instance: Any?): Boolean { val anchor = scope.anchor ?: return false val slotTable = reader.table - val location = anchor.toIndexFor(slotTable) + val location = anchor.asGapAnchor().toIndexFor(slotTable) if (isComposing && location >= reader.currentGroup) { // if we are invalidating a scope that is going to be traversed during this // composition. @@ -2304,7 +2305,7 @@ internal class GapComposer( changeListWriter.withChangeList(lateChanges) { changeListWriter.resetSlots() references.fastForEach { (to, from) -> - val anchor = to.anchor + val anchor = to.anchor.asGapAnchor() val toSlotTable = to.slotStorage.asGapBufferSlotTable() val location = toSlotTable.anchorIndex(anchor) val effectiveNodeIndex = IntRef() @@ -2354,7 +2355,7 @@ internal class GapComposer( val resolvedState = parentContext.movableContentStateResolve(from) val resolvedSlotTable = resolvedState?.slotStorage?.asGapBufferSlotTable() val fromTable = resolvedSlotTable ?: from.slotStorage.asGapBufferSlotTable() - val fromAnchor = resolvedSlotTable?.anchor(0) ?: from.anchor + val fromAnchor = (resolvedSlotTable?.anchor(0) ?: from.anchor).asGapAnchor() val nodesToInsert = fromTable.collectNodesFrom(fromAnchor) // Insert nodes if necessary @@ -2383,7 +2384,7 @@ internal class GapComposer( fromTable.read { reader -> withReader(reader) { - val newLocation = fromTable.anchorIndex(fromAnchor) + val newLocation = fromTable.anchorIndex(fromAnchor.asGapAnchor()) reader.reposition(newLocation) changeListWriter.moveReaderToAbsolute(newLocation) val offsetChanges = ChangeList() @@ -2591,7 +2592,7 @@ internal class GapComposer( // that are no longer in the slot table. for (i in invalidations.lastIndex downTo 0) { val invalidation = invalidations[i] - val anchor = invalidation.scope.anchor + val anchor = invalidation.scope.anchor?.asGapAnchor() if (anchor != null && anchor.valid) { if (invalidation.location != anchor.location) invalidation.location = anchor.location @@ -2603,7 +2604,7 @@ internal class GapComposer( // Add the requested invalidations invalidationsRequested.map.forEach { scope, instances -> scope as RecomposeScopeImpl - val location = scope.anchor?.location ?: return@forEach + val location = scope.anchor?.asGapAnchor()?.location ?: return@forEach invalidations.add( Invalidation(scope, location, instances.takeUnless { it === ScopeInvalidated }) ) @@ -2689,7 +2690,7 @@ internal class GapComposer( runtimeCheck(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" } } - private fun recordInsert(anchor: Anchor) { + private fun recordInsert(anchor: GapAnchor) { if (insertFixups.isEmpty()) { changeListWriter.insertSlots(anchor, insertTable) } else { @@ -3330,7 +3331,7 @@ private fun Boolean.asInt() = if (this) 1 else 0 private fun Int.asBool() = this != 0 -private fun SlotTable.collectNodesFrom(anchor: Anchor): List { +private fun SlotTable.collectNodesFrom(anchor: GapAnchor): List { val result = mutableListOf() read { reader -> val index = anchorIndex(anchor) diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MovableContent.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MovableContent.kt index bb70aa690b340..2b33212cec4f7 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MovableContent.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MovableContent.kt @@ -17,7 +17,6 @@ package androidx.compose.runtime import androidx.compose.runtime.annotation.RememberInComposition -import androidx.compose.runtime.composer.gapbuffer.Anchor /** * Convert a lambda into one that moves the remembered state and nodes created in a previous call to diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt index 2b3e62e092730..d8757b436b051 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt @@ -19,7 +19,7 @@ package androidx.compose.runtime import androidx.collection.MutableObjectIntMap import androidx.collection.MutableScatterMap import androidx.collection.ScatterSet -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter import androidx.compose.runtime.snapshots.fastAny @@ -426,7 +426,7 @@ internal class RecomposeScopeImpl(internal var owner: RecomposeScopeOwner?) : companion object { internal fun adoptAnchoredScopes( slots: SlotWriter, - anchors: List, + anchors: List, newOwner: RecomposeScopeOwner, ) { if (anchors.isNotEmpty()) { @@ -439,7 +439,7 @@ internal class RecomposeScopeImpl(internal var owner: RecomposeScopeOwner?) : } } - internal fun hasAnchoredRecomposeScopes(slots: SlotTable, anchors: List) = + internal fun hasAnchoredRecomposeScopes(slots: SlotTable, anchors: List) = anchors.isNotEmpty() && anchors.fastAny { slots.ownsAnchor(it) && diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/GroupSourceInformation.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/GroupSourceInformation.kt new file mode 100644 index 0000000000000..26855b43681d3 --- /dev/null +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/GroupSourceInformation.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.runtime.composer + +internal interface GroupSourceInformation { + val closed: Boolean + val dataEndOffset: Int + val dataStartOffset: Int + val key: Int + val sourceInformation: String? + val groups: ArrayList? +} diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/GapAnchor.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/GapAnchor.kt new file mode 100644 index 0000000000000..671fcaa3511f6 --- /dev/null +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/GapAnchor.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.runtime.composer.gapbuffer + +import androidx.compose.runtime.Anchor +import androidx.compose.runtime.composeRuntimeError + +/** + * An [Anchor] tracks a groups as its index changes due to other groups being inserted and removed + * before it. If the group the [Anchor] is tracking is removed, directly or indirectly, [valid] will + * return false. The current index of the group can be determined by passing either the [SlotTable] + * or [] to [toIndexFor]. If a [SlotWriter] is active, it must be used instead of the [SlotTable] as + * the anchor index could have shifted due to operations performed on the writer. + */ +internal class GapAnchor(loc: Int) : Anchor { + internal var location: Int = loc + + override val valid + get() = location != Int.MIN_VALUE + + fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this) + + fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this) + + override fun toString(): String { + return "${super.toString()}{ location = $location }" + } +} + +internal fun Anchor.asGapAnchor(): GapAnchor = + this as? GapAnchor ?: composeRuntimeError("Inconsistent composition") diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTable.kt index ec6e22833c2c1..b7a204bff0e4d 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTable.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTable.kt @@ -28,6 +28,7 @@ import androidx.collection.emptyScatterMap import androidx.collection.mutableIntListOf import androidx.collection.mutableIntSetOf import androidx.collection.mutableScatterMapOf +import androidx.compose.runtime.Anchor import androidx.compose.runtime.Applier import androidx.compose.runtime.Composer import androidx.compose.runtime.IntStack @@ -44,6 +45,7 @@ import androidx.compose.runtime.collection.fastCopyInto import androidx.compose.runtime.collection.fastFilter import androidx.compose.runtime.collection.sortedBy import androidx.compose.runtime.composeRuntimeError +import androidx.compose.runtime.composer.GroupSourceInformation import androidx.compose.runtime.deactivateCurrentGroup import androidx.compose.runtime.debugRuntimeCheck import androidx.compose.runtime.extractMovableContentAtCurrent @@ -159,10 +161,10 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable = arrayListOf() + internal var anchors: ArrayList = arrayListOf() /** A map of source information to anchor. */ - internal var sourceInformationMap: HashMap? = null + internal var sourceInformationMap: HashMap? = null /** * A map of source marker numbers to their, potentially indirect, parent key. This is recorded @@ -239,20 +241,21 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable= 0 && anchors[it] == anchor } } @@ -283,20 +286,21 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable?, + sourceInformationMap: HashMap?, ) { runtimeCheck(reader.table === this && readers > 0) { "Unexpected reader close()" } readers-- @@ -323,8 +327,8 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable, slotsSize: Int, - anchors: ArrayList, - sourceInformationMap: HashMap?, + anchors: ArrayList, + sourceInformationMap: HashMap?, calledByMap: MutableIntObjectMap?, ) { requirePrecondition(writer.table === this && this.writer) { "Unexpected writer close()" } @@ -341,8 +345,8 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable, slotsSize: Int, - anchors: ArrayList, - sourceInformationMap: HashMap?, + anchors: ArrayList, + sourceInformationMap: HashMap?, calledByMap: MutableIntObjectMap?, ) { // Adopt the slots from the writer @@ -368,7 +372,7 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable? { - val anchors = mutableListOf() + val anchors = mutableListOf() val scopes = mutableListOf() var allScopesFound = true val set = @@ -388,7 +392,9 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable when (item) { - is Anchor -> { + is GapAnchor -> { requirePrecondition(item.valid) { "Source map contains invalid anchor" } requirePrecondition(ownsAnchor(item)) { "Source map anchor is not owned by the slot table" } } - is GroupSourceInformation -> verifySourceGroup(item) + is GapGroupSourceInformation -> verifySourceGroup(item) } } } @@ -587,7 +593,7 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable - scope.anchor?.let { anchor -> + scope.anchor?.asGapAnchor()?.let { anchor -> checkPrecondition(scope in slotsOf(anchor.toIndexFor(this))) { val dataIndex = slots.indexOf(scope) "Misaligned anchor $anchor in scope $scope encountered, scope found at " + @@ -628,7 +634,9 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable, ): ScatterMap { val referencesToExtract = - references.fastFilter { ownsAnchor(it.anchor) }.sortedBy { anchorIndex(it.anchor) } + references + .fastFilter { ownsAnchor(it.anchor.asGapAnchor()) } + .sortedBy { anchorIndex(it.anchor.asGapAnchor()) } if (referencesToExtract.isEmpty()) return emptyScatterMap() val result = mutableScatterMapOf() write { writer -> @@ -651,7 +659,7 @@ internal class SlotTable : SlotStorage(), CompositionData, Iterable - val newGroup = writer.anchorIndex(reference.anchor) + val newGroup = writer.anchorIndex(reference.anchor.asGapAnchor()) val newParent = writer.parent(newGroup) closeToGroupContaining(newParent) openParent(newParent) @@ -817,39 +825,17 @@ private inline fun Array.fastForEach(action: (T) -> Unit) { for (i in 0 until size) action(this[i]) } -/** - * An [Anchor] tracks a groups as its index changes due to other groups being inserted and removed - * before it. If the group the [Anchor] is tracking is removed, directly or indirectly, [valid] will - * return false. The current index of the group can be determined by passing either the [SlotTable] - * or [] to [toIndexFor]. If a [SlotWriter] is active, it must be used instead of the [SlotTable] as - * the anchor index could have shifted due to operations performed on the writer. - */ -internal class Anchor(loc: Int) { - internal var location: Int = loc - - val valid - get() = location != Int.MIN_VALUE - - fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this) - - fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this) - - override fun toString(): String { - return "${super.toString()}{ location = $location }" - } -} - -internal class GroupSourceInformation( - val key: Int, - var sourceInformation: String?, - val dataStartOffset: Int, -) { - var groups: ArrayList? = null - var closed = false - var dataEndOffset: Int = 0 +internal class GapGroupSourceInformation( + override val key: Int, + override var sourceInformation: String?, + override val dataStartOffset: Int, +) : GroupSourceInformation { + override var groups: ArrayList? = null + override var closed = false + override var dataEndOffset: Int = 0 fun startGrouplessCall(key: Int, sourceInformation: String, dataOffset: Int) { - openInformation().add(GroupSourceInformation(key, sourceInformation, dataOffset)) + openInformation().add(GapGroupSourceInformation(key, sourceInformation, dataOffset)) } fun endGrouplessCall(dataOffset: Int) { @@ -871,7 +857,7 @@ internal class GroupSourceInformation( val anchor = writer.tryAnchor(predecessor) if (anchor != null) { groups.fastIndexOf { - it == anchor || (it is GroupSourceInformation && it.hasAnchor(anchor)) + it == anchor || (it is GapGroupSourceInformation && it.hasAnchor(anchor)) } } else 0 } else 0 @@ -884,10 +870,10 @@ internal class GroupSourceInformation( } // Return the current open nested source information or this. - private fun openInformation(): GroupSourceInformation = + private fun openInformation(): GapGroupSourceInformation = (groups?.let { groups -> - groups.fastLastOrNull { it is GroupSourceInformation && !it.closed } - } as? GroupSourceInformation) + groups.fastLastOrNull { it is GapGroupSourceInformation && !it.closed } + } as? GapGroupSourceInformation) ?.openInformation() ?: this private fun add(group: Any /* Anchor | GroupSourceInformation */) { @@ -896,19 +882,19 @@ internal class GroupSourceInformation( groups.add(group) } - private fun hasAnchor(anchor: Anchor): Boolean = + private fun hasAnchor(anchor: GapAnchor): Boolean = groups?.fastAny { - it == anchor || (it is GroupSourceInformation && it.hasAnchor(anchor)) + it == anchor || (it is GapGroupSourceInformation && it.hasAnchor(anchor)) } == true - fun removeAnchor(anchor: Anchor): Boolean { + fun removeAnchor(anchor: GapAnchor): Boolean { val groups = groups if (groups != null) { var index = groups.size - 1 while (index >= 0) { when (val item = groups[index]) { - is Anchor -> if (item == anchor) groups.removeAt(index) - is GroupSourceInformation -> + is GapAnchor -> if (item == anchor) groups.removeAt(index) + is GapGroupSourceInformation -> if (!item.removeAnchor(anchor)) { groups.removeAt(index) } @@ -968,7 +954,7 @@ internal class SlotReader( * A local copy of the [sourceInformationMap] being created to be merged into [table] when the * reader closes. */ - private var sourceInformationMap: HashMap? = null + private var sourceInformationMap: HashMap? = null /** True if the reader has been closed */ var closed: Boolean = false @@ -1106,7 +1092,7 @@ internal class SlotReader( get() = if (currentGroup < currentEnd) groups.node(currentGroup) else null /** Get the group key at [anchor]. This return 0 if the anchor is not valid. */ - fun groupKey(anchor: Anchor) = if (anchor.valid) groups.key(table.anchorIndex(anchor)) else 0 + fun groupKey(anchor: GapAnchor) = if (anchor.valid) groups.key(table.anchorIndex(anchor)) else 0 /** Returns true when the group at [index] was marked with [SlotWriter.markGroup]. */ fun hasMark(index: Int) = groups.hasMark(index) @@ -1332,7 +1318,7 @@ internal class SlotReader( /** Create an anchor to the current reader location or [index]. */ fun anchor(index: Int = currentGroup) = - table.anchors.getOrAdd(index, groupsSize) { Anchor(index) } + table.anchors.getOrAdd(index, groupsSize) { GapAnchor(index) } private fun IntArray.node(index: Int) = if (isNode(index)) { @@ -1391,7 +1377,7 @@ internal class SlotWriter( private var slots: Array = table.slots /** A copy of the [SlotTable.anchors] to avoid having to index through [table]. */ - private var anchors: ArrayList = table.anchors + private var anchors: ArrayList = table.anchors /** A copy of [SlotTable.sourceInformationMap] to avoid having to index through [table] */ private var sourceInformationMap = table.sourceInformationMap @@ -1549,7 +1535,7 @@ internal class SlotWriter( } /** Return the node at [anchor] if it is a node group or null. */ - fun node(anchor: Anchor) = node(anchor.toIndexFor(this)) + fun node(anchor: GapAnchor) = node(anchor.toIndexFor(this)) /** Return the index of the nearest group that contains [currentGroup]. */ var parent: Int = -1 @@ -1562,7 +1548,7 @@ internal class SlotWriter( * Return the index of the parent for the group referenced by [anchor]. If the anchor is not * valid it returns -1. */ - fun parent(anchor: Anchor) = if (anchor.valid) groups.parent(anchorIndex(anchor)) else -1 + fun parent(anchor: GapAnchor) = if (anchor.valid) groups.parent(anchorIndex(anchor)) else -1 /** True if the writer has been closed */ var closed = false @@ -1630,7 +1616,7 @@ internal class SlotWriter( } /** Append a slot to the [parent] group. */ - fun appendSlot(anchor: Anchor, value: Any?) { + fun appendSlot(anchor: GapAnchor, value: Any?) { runtimeCheck(insertCount == 0) { "Can only append a slot if not current inserting" } var previousCurrentSlot = currentSlot var previousCurrentSlotEnd = currentSlotEnd @@ -1730,9 +1716,9 @@ internal class SlotWriter( private fun groupSourceInformationFor( parent: Int, sourceInformation: String?, - ): GroupSourceInformation? = + ): GapGroupSourceInformation? = sourceInformationMap?.getOrPut(anchor(parent)) { - val result = GroupSourceInformation(0, sourceInformation, 0) + val result = GapGroupSourceInformation(0, sourceInformation, 0) // If we called from a groupless call then the groups added before this call // are not reflected in this group information so they need to be added now @@ -1753,7 +1739,8 @@ internal class SlotWriter( fun updateNode(value: Any?) = updateNodeOfGroup(currentGroup, value) /** Update the node of a the group at [anchor] to [value]. */ - fun updateNode(anchor: Anchor, value: Any?) = updateNodeOfGroup(anchor.toIndexFor(this), value) + fun updateNode(anchor: GapAnchor, value: Any?) = + updateNodeOfGroup(anchor.toIndexFor(this), value) /** Updates the node of the parent group. */ fun updateParentNode(value: Any?) = updateNodeOfGroup(parent, value) @@ -1812,7 +1799,7 @@ internal class SlotWriter( * Read the [index] slot at the group at [anchor]. Returns [Composer.Empty] if the slot is empty * (e.g. out of range). */ - fun slot(anchor: Anchor, index: Int) = slot(anchorIndex(anchor), index) + fun slot(anchor: GapAnchor, index: Int) = slot(anchorIndex(anchor), index) /** * Read the [index] slot at the group at index [groupIndex]. Returns [Composer.Empty] if the @@ -1885,7 +1872,7 @@ internal class SlotWriter( * Seek the current location to [anchor]. The [anchor] must be an anchor to a possibly indirect * child of [parent]. */ - fun seek(anchor: Anchor) = advanceBy(anchor.toIndexFor(this) - currentGroup) + fun seek(anchor: GapAnchor) = advanceBy(anchor.toIndexFor(this) - currentGroup) /** Skip to the end of the current group. */ fun skipToGroupEnd() { @@ -2139,7 +2126,7 @@ internal class SlotWriter( } } - fun ensureStarted(anchor: Anchor) = ensureStarted(anchor.toIndexFor(this)) + fun ensureStarted(anchor: GapAnchor) = ensureStarted(anchor.toIndexFor(this)) /** Skip the current group. Returns the number of nodes skipped by skipping the group. */ fun skipGroup(): Int { @@ -2424,7 +2411,7 @@ internal class SlotWriter( } } - fun inGroup(groupAnchor: Anchor, anchor: Anchor): Boolean { + fun inGroup(groupAnchor: GapAnchor, anchor: GapAnchor): Boolean { val group = anchorIndex(groupAnchor) val groupEnd = group + groups.groupSize(group) return anchor.location in group until groupEnd @@ -2438,7 +2425,7 @@ internal class SlotWriter( updateFromCursor: Boolean, updateToCursor: Boolean, removeSourceGroup: Boolean = true, - ): List { + ): List { val groupsToMove = fromWriter.groupSize(fromIndex) val sourceGroupsEnd = fromIndex + groupsToMove val sourceSlotsStart = fromWriter.dataIndex(fromIndex) @@ -2523,7 +2510,7 @@ internal class SlotWriter( val anchors = if (startAnchors < endAnchors) { val sourceAnchors = fromWriter.anchors - val anchors = ArrayList(endAnchors - startAnchors) + val anchors = ArrayList(endAnchors - startAnchors) // update the anchor locations to their new location val anchorDelta = currentGroup - fromIndex @@ -2637,7 +2624,7 @@ internal class SlotWriter( * * This requires [writer] be inserting and this writer to not be inserting. */ - fun moveTo(anchor: Anchor, offset: Int, writer: SlotWriter): List { + fun moveTo(anchor: GapAnchor, offset: Int, writer: SlotWriter): List { runtimeCheck(writer.insertCount > 0) runtimeCheck(insertCount == 0) runtimeCheck(anchor.valid) @@ -2687,7 +2674,7 @@ internal class SlotWriter( * * @return a list of the anchors that were moved */ - fun moveFrom(table: SlotTable, index: Int, removeSourceGroup: Boolean = true): List { + fun moveFrom(table: SlotTable, index: Int, removeSourceGroup: Boolean = true): List { runtimeCheck(insertCount > 0) if ( @@ -2757,7 +2744,7 @@ internal class SlotWriter( * * @return a list of the anchors that were moved. */ - fun moveIntoGroupFrom(offset: Int, table: SlotTable, index: Int): List { + fun moveIntoGroupFrom(offset: Int, table: SlotTable, index: Int): List { runtimeCheck(insertCount <= 0 && groupSize(currentGroup + offset) == 1) val previousCurrentGroup = currentGroup val previousCurrentSlot = currentSlot @@ -2778,9 +2765,9 @@ internal class SlotWriter( } /** Allocate an anchor to the current group or [index]. */ - fun anchor(index: Int = currentGroup): Anchor = + fun anchor(index: Int = currentGroup): GapAnchor = anchors.getOrAdd(index, size) { - Anchor(if (index <= groupGapStart) index else -(size - index)) + GapAnchor(if (index <= groupGapStart) index else -(size - index)) } fun markGroup(group: Int = parent) { @@ -2840,7 +2827,7 @@ internal class SlotWriter( } /** Return the current anchor location while changing the slot table. */ - fun anchorIndex(anchor: Anchor) = anchor.location.let { if (it < 0) size + it else it } + fun anchorIndex(anchor: GapAnchor) = anchor.location.let { if (it < 0) size + it else it } override fun toString(): String { return "SlotWriter(current = $currentGroup end=$currentGroupEnd size = $size " + @@ -3153,7 +3140,7 @@ internal class SlotWriter( } else false } - internal fun sourceInformationOf(group: Int): GroupSourceInformation? = + internal fun sourceInformationOf(group: Int): GapGroupSourceInformation? = sourceInformationMap?.let { map -> tryAnchor(group)?.let { anchor -> map[anchor] } } internal fun tryAnchor(group: Int) = @@ -3221,7 +3208,7 @@ internal class SlotWriter( private fun removeAnchors( gapStart: Int, size: Int, - sourceInformationMap: HashMap?, + sourceInformationMap: HashMap?, ): Boolean { val gapLen = groupGapLen val removeEnd = gapStart + size @@ -3257,7 +3244,7 @@ internal class SlotWriter( // Remove all the anchors in range from the original location val index = anchors.locationOf(originalLocation, groupsSize) - val removedAnchors = mutableListOf() + val removedAnchors = mutableListOf() if (index >= 0) { while (index < anchors.size) { val anchor = anchors[index] @@ -3548,7 +3535,7 @@ private class SlotTableGroup( } override fun find(identityToFind: Any): CompositionGroup? { - fun findAnchoredGroup(anchor: Anchor): CompositionGroup? { + fun findAnchoredGroup(anchor: GapAnchor): CompositionGroup? { if (table.ownsAnchor(anchor)) { val anchorGroup = table.anchorIndex(anchor) if (anchorGroup >= group && (anchorGroup - group < table.groups.groupSize(group))) { @@ -3562,7 +3549,7 @@ private class SlotTableGroup( group.compositionGroups.drop(index).firstOrNull() return when (identityToFind) { - is Anchor -> findAnchoredGroup(identityToFind) + is GapAnchor -> findAnchoredGroup(identityToFind) is SourceInformationSlotTableGroupIdentity -> find(identityToFind.parentIdentity)?.let { findRelativeGroup(it, identityToFind.index) @@ -3611,7 +3598,7 @@ private class RelativeGroupPath(val parent: SourceInformationGroupPath, val inde private class SourceInformationSlotTableGroup( val table: SlotTable, val parent: Int, - val sourceInformation: GroupSourceInformation, + val sourceInformation: GapGroupSourceInformation, val identityPath: SourceInformationGroupPath, ) : CompositionGroup, Iterable { override val key: Any = sourceInformation.key @@ -3693,7 +3680,7 @@ private class DataIterator(val table: SlotTable, group: Int) : Iterable, I private class SourceInformationGroupDataIterator( val table: SlotTable, group: Int, - sourceInformation: GroupSourceInformation, + sourceInformation: GapGroupSourceInformation, ) : Iterable, Iterator { private val base = table.groups.dataAnchor(group) private val start: Int = sourceInformation.dataStartOffset @@ -3709,7 +3696,7 @@ private class SourceInformationGroupDataIterator( // Filter any groups val groups = sourceInformation.groups ?: return@also groups.fastForEach { info -> - if (info is GroupSourceInformation) { + if (info is GapGroupSourceInformation) { it.setRange(info.dataStartOffset, info.dataEndOffset) } } @@ -3872,7 +3859,7 @@ private val Long.firstBitSet private class SourceInformationGroupIterator( val table: SlotTable, val parent: Int, - val group: GroupSourceInformation, + val group: GapGroupSourceInformation, val path: SourceInformationGroupPath, ) : Iterator { private val version = table.version @@ -3882,8 +3869,8 @@ private class SourceInformationGroupIterator( override fun next(): CompositionGroup { return when (val group = group.groups?.get(index++)) { - is Anchor -> SlotTableGroup(table, group.location, version) - is GroupSourceInformation -> + is GapAnchor -> SlotTableGroup(table, group.location, version) + is GapGroupSourceInformation -> SourceInformationSlotTableGroup( table = table, parent = parent, @@ -4104,11 +4091,11 @@ private fun IntArray.updateGroupKey(address: Int, key: Int) { this[arrayIndex + Key_Offset] = key } -private inline fun ArrayList.getOrAdd( +private inline fun ArrayList.getOrAdd( index: Int, effectiveSize: Int, - block: () -> Anchor, -): Anchor { + block: () -> GapAnchor, +): GapAnchor { val location = search(index, effectiveSize) return if (location < 0) { val anchor = block() @@ -4117,13 +4104,13 @@ private inline fun ArrayList.getOrAdd( } else get(location) } -private fun ArrayList.find(index: Int, effectiveSize: Int): Anchor? { +private fun ArrayList.find(index: Int, effectiveSize: Int): GapAnchor? { val location = search(index, effectiveSize) return if (location >= 0) get(location) else null } /** This is inlined here instead to avoid allocating a lambda for the compare when this is used. */ -private fun ArrayList.search(location: Int, effectiveSize: Int): Int { +private fun ArrayList.search(location: Int, effectiveSize: Int): Int { var low = 0 var high = size - 1 @@ -4145,7 +4132,7 @@ private fun ArrayList.search(location: Int, effectiveSize: Int): Int { * A wrapper on [search] that always returns an index in to [this] even if [index] is not in the * array list. */ -private fun ArrayList.locationOf(index: Int, effectiveSize: Int) = +private fun ArrayList.locationOf(index: Int, effectiveSize: Int) = search(index, effectiveSize).let { if (it >= 0) it else -(it + 1) } /** diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ChangeList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ChangeList.kt index cb8107236292b..8ec0581c8fef1 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ChangeList.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ChangeList.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.RecomposeScopeImpl import androidx.compose.runtime.RememberManager import androidx.compose.runtime.RememberObserverHolder import androidx.compose.runtime.SlotStorage -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter import androidx.compose.runtime.composer.gapbuffer.asGapBufferSlotTable @@ -135,14 +135,14 @@ internal class ChangeList : Changes() { } } - fun pushUpdateAnchoredValue(value: Any?, anchor: Anchor, groupSlotIndex: Int) { + fun pushUpdateAnchoredValue(value: Any?, anchor: GapAnchor, groupSlotIndex: Int) { operations.push(UpdateAnchoredValue) { setObjects(UpdateAnchoredValue.Value, value, UpdateAnchoredValue.Anchor, anchor) setInt(UpdateAnchoredValue.GroupSlotIndex, groupSlotIndex) } } - fun pushAppendValue(anchor: Anchor, value: Any?) { + fun pushAppendValue(anchor: GapAnchor, value: Any?) { operations.push(AppendValue) { setObjects(AppendValue.Anchor, anchor, AppendValue.Value, value) } @@ -168,7 +168,7 @@ internal class ChangeList : Changes() { operations.push(EnsureRootGroupStarted) } - fun pushEnsureGroupStarted(anchor: Anchor) { + fun pushEnsureGroupStarted(anchor: GapAnchor) { operations.push(EnsureGroupStarted) { setObject(EnsureGroupStarted.Anchor, anchor) } } @@ -184,13 +184,13 @@ internal class ChangeList : Changes() { operations.push(RemoveCurrentGroup) } - fun pushInsertSlots(anchor: Anchor, from: SlotTable) { + fun pushInsertSlots(anchor: GapAnchor, from: SlotTable) { operations.push(InsertSlots) { setObjects(InsertSlots.Anchor, anchor, InsertSlots.FromSlotTable, from) } } - fun pushInsertSlots(anchor: Anchor, from: SlotTable, fixups: FixupList) { + fun pushInsertSlots(anchor: GapAnchor, from: SlotTable, fixups: FixupList) { operations.push(InsertSlotsWithFixups) { setObjects( InsertSlotsWithFixups.Anchor, @@ -261,7 +261,7 @@ internal class ChangeList : Changes() { operations.push(SideEffect) { setObject(SideEffect.Effect, effect) } } - fun pushDetermineMovableContentNodeIndex(effectiveNodeIndexOut: IntRef, anchor: Anchor) { + fun pushDetermineMovableContentNodeIndex(effectiveNodeIndexOut: IntRef, anchor: GapAnchor) { operations.push(DetermineMovableContentNodeIndex) { setObjects( DetermineMovableContentNodeIndex.EffectiveNodeIndexOut, diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ComposerChangeListWriter.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ComposerChangeListWriter.kt index 72cf30a017d67..812e5a4d78f6f 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ComposerChangeListWriter.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/ComposerChangeListWriter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.MovableContentStateReference import androidx.compose.runtime.RecomposeScopeImpl import androidx.compose.runtime.RememberObserverHolder import androidx.compose.runtime.Stack -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotReader import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.internal.IntRef @@ -150,7 +150,7 @@ internal class ComposerChangeListWriter( } } - private fun ensureGroupStarted(anchor: Anchor) { + private fun ensureGroupStarted(anchor: GapAnchor) { pushSlotTableOperationPreamble() changeList.pushEnsureGroupStarted(anchor) startedGroup = true @@ -210,12 +210,12 @@ internal class ComposerChangeListWriter( changeList.pushUpdateValue(value, groupSlotIndex) } - fun updateAnchoredValue(value: Any?, anchor: Anchor, groupSlotIndex: Int) { + fun updateAnchoredValue(value: Any?, anchor: GapAnchor, groupSlotIndex: Int) { // Because this uses an anchor, it can be performed without positioning the writer. changeList.pushUpdateAnchoredValue(value, anchor, groupSlotIndex) } - fun appendValue(anchor: Anchor, value: Any?) { + fun appendValue(anchor: GapAnchor, value: Any?) { // Because this uses an anchor, it can be performed without positioning the writer. changeList.pushAppendValue(anchor, value) } @@ -271,14 +271,14 @@ internal class ComposerChangeListWriter( writersReaderDelta += reader.groupSize } - fun insertSlots(anchor: Anchor, from: SlotTable) { + fun insertSlots(anchor: GapAnchor, from: SlotTable) { pushPendingUpsAndDowns() pushSlotEditingOperationPreamble() realizeNodeMovementOperations() changeList.pushInsertSlots(anchor, from) } - fun insertSlots(anchor: Anchor, from: SlotTable, fixups: FixupList) { + fun insertSlots(anchor: GapAnchor, from: SlotTable, fixups: FixupList) { pushPendingUpsAndDowns() pushSlotEditingOperationPreamble() realizeNodeMovementOperations() @@ -406,7 +406,7 @@ internal class ComposerChangeListWriter( changeList.pushSideEffect(effect) } - fun determineMovableContentNodeIndex(effectiveNodeIndexOut: IntRef, anchor: Anchor) { + fun determineMovableContentNodeIndex(effectiveNodeIndexOut: IntRef, anchor: GapAnchor) { pushPendingUpsAndDowns() changeList.pushDetermineMovableContentNodeIndex(effectiveNodeIndexOut, anchor) } diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/FixupList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/FixupList.kt index e07ea60618fac..d6b1ea9608a20 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/FixupList.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/FixupList.kt @@ -18,7 +18,7 @@ package androidx.compose.runtime.composer.gapbuffer.changelist import androidx.compose.runtime.Applier import androidx.compose.runtime.RememberManager -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotWriter import androidx.compose.runtime.runtimeCheck @@ -56,7 +56,7 @@ internal class FixupList : OperationsDebugStringFormattable() { ) } - fun createAndInsertNode(factory: () -> Any?, insertIndex: Int, groupAnchor: Anchor) { + fun createAndInsertNode(factory: () -> Any?, insertIndex: Int, groupAnchor: GapAnchor) { operations.push(Operation.InsertNodeFixup) { setObject(Operation.InsertNodeFixup.Factory, factory) setInt(Operation.InsertNodeFixup.InsertIndex, insertIndex) diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/Operation.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/Operation.kt index 78731a6ee5049..917da6569c3af 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/Operation.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/composer/gapbuffer/changelist/Operation.kt @@ -30,7 +30,7 @@ import androidx.compose.runtime.RememberManager import androidx.compose.runtime.RememberObserverHolder import androidx.compose.runtime.TestOnly import androidx.compose.runtime.composeRuntimeError -import androidx.compose.runtime.composer.gapbuffer.Anchor +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter import androidx.compose.runtime.composer.gapbuffer.asGapBufferSlotTable @@ -65,7 +65,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { } } - protected open fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? = null + protected open fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): GapAnchor? = null protected abstract fun OperationArgContainer.execute( applier: Applier<*>, @@ -256,7 +256,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { object AppendValue : Operation(objects = 2) { inline val Anchor - get() = ObjectParameter(0) + get() = ObjectParameter(0) inline val Value get() = ObjectParameter(1) @@ -356,7 +356,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { get() = ObjectParameter(0) inline val Anchor - get() = ObjectParameter(1) + get() = ObjectParameter(1) inline val GroupSlotIndex get() = 0 @@ -432,7 +432,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { object EnsureGroupStarted : Operation(objects = 1) { inline val Anchor - get() = ObjectParameter(0) + get() = ObjectParameter(0) override fun objectParamName(parameter: ObjectParameter<*>) = when (parameter) { @@ -621,7 +621,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { object InsertSlots : Operation(objects = 2) { inline val Anchor - get() = ObjectParameter(0) + get() = ObjectParameter(0) inline val FromSlotTable get() = ObjectParameter(1) @@ -654,7 +654,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { object InsertSlotsWithFixups : Operation(objects = 3) { inline val Anchor - get() = ObjectParameter(0) + get() = ObjectParameter(0) inline val FromSlotTable get() = ObjectParameter(1) @@ -706,7 +706,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { get() = 0 inline val GroupAnchor - get() = ObjectParameter(1) + get() = ObjectParameter(1) override fun intParamName(parameter: IntParameter) = when (parameter) { @@ -721,7 +721,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { else -> super.objectParamName(parameter) } - override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? = + override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): GapAnchor? = getObject(GroupAnchor) override fun OperationArgContainer.execute( @@ -746,7 +746,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { get() = 0 inline val GroupAnchor - get() = ObjectParameter(0) + get() = ObjectParameter(0) override fun intParamName(parameter: IntParameter) = when (parameter) { @@ -760,7 +760,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { else -> super.objectParamName(parameter) } - override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? = + override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): GapAnchor? = getObject(GroupAnchor) override fun OperationArgContainer.execute( @@ -809,7 +809,7 @@ internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) { get() = ObjectParameter(0) inline val Anchor - get() = ObjectParameter(1) + get() = ObjectParameter(1) override fun objectParamName(parameter: ObjectParameter<*>) = when (parameter) { @@ -1077,7 +1077,7 @@ private fun currentNodeIndex(slots: SlotWriter): Int { return index } -private fun positionToInsert(slots: SlotWriter, anchor: Anchor, applier: Applier): Int { +private fun positionToInsert(slots: SlotWriter, anchor: GapAnchor, applier: Applier): Int { val destination = slots.anchorIndex(anchor) runtimeCheck(slots.currentGroup < destination) positionToParentOf(slots, applier, destination) @@ -1102,7 +1102,7 @@ private fun positionToInsert(slots: SlotWriter, anchor: Anchor, applier: Applier private inline fun withCurrentStackTrace( errorContext: OperationErrorContext?, writer: SlotWriter, - location: Anchor?, + location: GapAnchor?, block: () -> Unit, ) { try { @@ -1117,7 +1117,7 @@ private inline fun withCurrentStackTrace( private fun Throwable.attachComposeStackTrace( errorContext: OperationErrorContext?, writer: SlotWriter, - anchor: Anchor?, + anchor: GapAnchor?, ): Throwable { if (errorContext == null) return this return attachComposeStackTrace { diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/ComposeStackTraceBuilder.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/ComposeStackTraceBuilder.kt index 1d6001c55cf6e..693de0dc0bd6e 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/ComposeStackTraceBuilder.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/ComposeStackTraceBuilder.kt @@ -15,15 +15,17 @@ */ package androidx.compose.runtime.tooling +import androidx.compose.runtime.Anchor import androidx.compose.runtime.Composer import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.GapComposer.CompositionContextHolder import androidx.compose.runtime.RememberObserverHolder -import androidx.compose.runtime.composer.gapbuffer.Anchor -import androidx.compose.runtime.composer.gapbuffer.GroupSourceInformation +import androidx.compose.runtime.composer.GroupSourceInformation +import androidx.compose.runtime.composer.gapbuffer.GapAnchor import androidx.compose.runtime.composer.gapbuffer.SlotReader import androidx.compose.runtime.composer.gapbuffer.SlotTable import androidx.compose.runtime.composer.gapbuffer.SlotWriter +import androidx.compose.runtime.composer.gapbuffer.asGapAnchor import androidx.compose.runtime.defaultsKey import androidx.compose.runtime.reference import androidx.compose.runtime.referenceKey @@ -31,16 +33,18 @@ import androidx.compose.runtime.snapshots.fastForEach internal class WriterTraceBuilder(private val writer: SlotWriter) : ComposeStackTraceBuilder() { override fun sourceInformationOf(anchor: Anchor): GroupSourceInformation? = - writer.sourceInformationOf(writer.anchorIndex(anchor)) + writer.sourceInformationOf(writer.anchorIndex(anchor.asGapAnchor())) - override fun groupKeyOf(anchor: Anchor): Int = writer.groupKey(writer.anchorIndex(anchor)) + override fun groupKeyOf(anchor: Anchor): Int = + writer.groupKey(writer.anchorIndex(anchor.asGapAnchor())) } internal class ReaderTraceBuilder(private val reader: SlotReader) : ComposeStackTraceBuilder() { override fun sourceInformationOf(anchor: Anchor): GroupSourceInformation? = - reader.table.sourceInformationOf(reader.table.anchorIndex(anchor)) + reader.table.sourceInformationOf(reader.table.anchorIndex(anchor.asGapAnchor())) - override fun groupKeyOf(anchor: Anchor): Int = reader.groupKey(reader.table.anchorIndex(anchor)) + override fun groupKeyOf(anchor: Anchor): Int = + reader.groupKey(reader.table.anchorIndex(anchor.asGapAnchor())) } internal abstract class ComposeStackTraceBuilder { @@ -86,7 +90,7 @@ internal abstract class ComposeStackTraceBuilder { sourceInfo != null && (sourceInfo.key == defaultsKey || (sourceInfo.key == 0 && - child is Anchor && + child is GapAnchor && groupKeyOf(child) == defaultsKey)) // If sourceInformation is null, it means that default group does not capture @@ -113,7 +117,7 @@ internal abstract class ComposeStackTraceBuilder { private fun sourceInformationOf(group: Any) = when (group) { - is Anchor -> sourceInformationOf(group) + is GapAnchor -> sourceInformationOf(group) is GroupSourceInformation -> group else -> error("Unexpected child source info $group") } @@ -177,7 +181,7 @@ internal abstract class ComposeStackTraceBuilder { children.fastForEach { child -> // find the edge that leads to target anchor when (child) { - is Anchor -> { + is GapAnchor -> { // edge found, return if (child == target) { appendTraceFrame(sourceInformation.key, sourceInformation, child) diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTableTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTableTests.kt index 66afb96225049..d97c921f85178 100644 --- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTableTests.kt +++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/composer/gapbuffer/SlotTableTests.kt @@ -259,7 +259,7 @@ class SlotTableTests { val slots = testSlotsNumbered() val anchors = slots.read { reader -> - val anchors = mutableListOf() + val anchors = mutableListOf() reader.startGroup() repeat(7) { repeat(10) { reader.skipGroup() } @@ -282,7 +282,7 @@ class SlotTableTests { val slots = testSlotsNumbered() val anchors = slots.read { reader -> - val anchors = mutableListOf() + val anchors = mutableListOf() reader.startGroup() repeat(7) { repeat(10) { reader.skipGroup() } @@ -372,8 +372,8 @@ class SlotTableTests { fun testAnchorMoves() { val slots = SlotTable() - fun buildSlots(range: List): Map { - val anchors = mutableMapOf() + fun buildSlots(range: List): Map { + val anchors = mutableMapOf() slots.write { writer -> fun item(value: Int, block: () -> Unit) { writer.startGroup(value) @@ -402,7 +402,7 @@ class SlotTableTests { return anchors } - fun validate(anchors: Map) { + fun validate(anchors: Map) { slots.verifyWellFormed() slots.read { reader -> for (anchor in anchors) { @@ -852,7 +852,7 @@ class SlotTableTests { fun testMoveGroup() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() fun buildSlots() { slots.write { writer -> @@ -1182,7 +1182,7 @@ class SlotTableTests { @Test fun testMovingOneGroup() { val sourceTable = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() sourceTable.write { writer -> writer.beginInsert() anchors.add(writer.anchor()) @@ -1224,7 +1224,7 @@ class SlotTableTests { @Test fun testMovingANodeGroup() { val sourceTable = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() sourceTable.write { writer -> writer.beginInsert() anchors.add(writer.anchor()) @@ -1270,7 +1270,7 @@ class SlotTableTests { @Test fun testMovingMultipleRootGroups() { val sourceTable = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() val moveCount = 5 sourceTable.write { writer -> writer.beginInsert() @@ -1338,7 +1338,7 @@ class SlotTableTests { } } - val movedAnchors = mutableSetOf() + val movedAnchors = mutableSetOf() slotsToMove.forEach { anchor -> if (anchor !in movedAnchors) { destinationTable.write { writer -> @@ -1383,7 +1383,7 @@ class SlotTableTests { val sourceTable = SlotTable() val destinationTable = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() sourceTable.write { writer -> writer.insert { writer.group(10) { @@ -1913,7 +1913,7 @@ class SlotTableTests { @Test fun testUpdatingNodeWithStartNode() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() slots.write { writer -> writer.insert { writer.group(treeRoot) { @@ -2110,7 +2110,7 @@ class SlotTableTests { @Test fun testUpdatingNodeWithUpdateParentNode() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() slots.write { writer -> writer.insert { writer.group(treeRoot) { @@ -2159,7 +2159,7 @@ class SlotTableTests { @Test fun testUpdatingNodeWithUpdateNode() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() slots.write { writer -> writer.insert { writer.group(treeRoot) { @@ -2204,7 +2204,7 @@ class SlotTableTests { @Test fun testUpdatingAuxWithUpdateAux() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() slots.write { writer -> writer.insert { writer.group(treeRoot) { @@ -2253,7 +2253,7 @@ class SlotTableTests { val innerGroupKeyBase = 1000 val dataCount = 5 - data class SlotInfo(val anchor: Anchor, val index: Int, val value: Int) + data class SlotInfo(val anchor: GapAnchor, val index: Int, val value: Int) slots.write { writer -> writer.insert { @@ -2424,7 +2424,7 @@ class SlotTableTests { @Test fun testMultipleRoots() { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() repeat(10) { slots.write { writer -> anchors.add(writer.anchor()) @@ -2444,13 +2444,13 @@ class SlotTableTests { @Test fun testCanRestoreParent() { - val anchors = mutableMapOf>() + val anchors = mutableMapOf>() val slots = SlotTable() slots.write { writer -> writer.beginInsert() writer.startGroup(treeRoot) repeat(10) { outerKey -> - val nestedAnchors = mutableListOf() + val nestedAnchors = mutableListOf() anchors[outerKey] = nestedAnchors writer.startGroup(outerKey) repeat(10) { innerKey -> @@ -2588,7 +2588,7 @@ class SlotTableTests { @Test fun testInsertOfZeroGroups() { - val sourceAnchors = mutableListOf() + val sourceAnchors = mutableListOf() val sourceTable = SlotTable().also { it.write { writer -> @@ -2611,8 +2611,8 @@ class SlotTableTests { } } - var container = Anchor(0) - val destinationAnchors = mutableListOf() + var container = GapAnchor(0) + val destinationAnchors = mutableListOf() val slots = SlotTable().also { it.write { writer -> @@ -3406,7 +3406,7 @@ class SlotTableTests { @Test fun canMoveTo() { val slots = SlotTable() - var anchor = Anchor(-1) + var anchor = GapAnchor(-1) // Create a slot table slots.write { writer -> @@ -3515,8 +3515,8 @@ class SlotTableTests { @Test fun canDeleteAGroupAfterMovingPartOfItsContent() { val slots = SlotTable() - var deleteAnchor = Anchor(-1) - var moveAnchor = Anchor(-1) + var deleteAnchor = GapAnchor(-1) + var moveAnchor = GapAnchor(-1) // Create a slot table slots.write { writer -> @@ -3587,9 +3587,9 @@ class SlotTableTests { @Test fun canMoveAndDeleteAfterAnInsert() { val slots = SlotTable() - var insertAnchor = Anchor(-1) - var deleteAnchor = Anchor(-1) - var moveAnchor = Anchor(-1) + var insertAnchor = GapAnchor(-1) + var deleteAnchor = GapAnchor(-1) + var moveAnchor = GapAnchor(-1) // Create a slot table slots.write { writer -> @@ -3653,7 +3653,7 @@ class SlotTableTests { @Test fun canMoveAGroupFromATableIntoAnotherGroup() { val slots = SlotTable().apply { collectSourceInformation() } - var insertAnchor = Anchor(-1) + var insertAnchor = GapAnchor(-1) // Create a slot table slots.write { writer -> @@ -4210,7 +4210,7 @@ class SlotTableTests { @Test fun canMoveAGroupFromATableIntoAnotherGroupAndModifyThatGroup() { val slots = SlotTable() - var insertAnchor = Anchor(-1) + var insertAnchor = GapAnchor(-1) // Create a slot table slots.write { writer -> @@ -4360,7 +4360,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_first_empty() { - var anchor: Anchor? = null + var anchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -4409,7 +4409,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_first_occupied() { - var anchor: Anchor? = null + var anchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -4470,7 +4470,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_after_occupied() { - var anchor: Anchor? = null + var anchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -4539,7 +4539,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_middle() { - var anchor: Anchor? = null + var anchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -4610,7 +4610,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_end() { - var anchor: Anchor? = null + var anchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -4672,7 +4672,7 @@ class SlotTableTests { @Test fun supportsAppendingSlots_ensureStarted() { - var insertAnchor: Anchor? = null + var insertAnchor: GapAnchor? = null val slots = SlotTable().apply { write { writer -> @@ -5569,9 +5569,9 @@ private fun validateItems(slots: SlotTable) { } } -private fun narrowTrees(): Pair> { +private fun narrowTrees(): Pair> { val slots = SlotTable() - val anchors = mutableListOf() + val anchors = mutableListOf() slots.write { writer -> writer.beginInsert() writer.startGroup(treeRoot) diff --git a/development/project-creator/README.md b/development/project-creator/README.md deleted file mode 100644 index 31732f20cfaab..0000000000000 --- a/development/project-creator/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Project creator - -This script will create a new library project and associated Gradle module using -a `groupId` and `artifactId`. - -It will use the `groupId` and `artifactId` to guess which configuration is most -appropriate for the project you are creating. - -## Basic usage - -```bash -./create_project.py androidx.foo foo-bar -``` - -## Project types - -The script leverages -`buildSrc/public/src/main/kotlin/androidx/build/SoftwareType.kt` to create the -recommended defaults for your project. However, you can override the options to -best fit your requirements. - -## Additional documentation - -See go/androidx-api-guidelines#module-creation (internal-only) or the -[equivalent page](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:docs/api_guidelines/modules.md#module-creation) -on public Android Code Search for advanced usage and solutions to common issues. - -## Development - -If you make any changes to the script, please update this `README` and make -corresponding updates at go/androidx-api-guidelines#module-creation. - -### Testing the script - -Generic project integration test -```bash -./create_project.py androidx.foo.bar bar-qux -``` - -Script test suite -```bash -./test_project_creator.py -``` diff --git a/development/project-creator/base-requirements.txt b/development/project-creator/base-requirements.txt deleted file mode 100644 index bb7386aabb0ad..0000000000000 --- a/development/project-creator/base-requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -virtualenv==20.31.2 --hash=sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11 -distlib==0.3.7 --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 -filelock==3.12.2 --hash=sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec -platformdirs==3.9.1 --hash=sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f diff --git a/development/project-creator/compose-template/groupId/OWNERS b/development/project-creator/compose-template/groupId/OWNERS deleted file mode 100644 index 15331e5f84a15..0000000000000 --- a/development/project-creator/compose-template/groupId/OWNERS +++ /dev/null @@ -1 +0,0 @@ -# example@google.com diff --git a/development/project-creator/compose-template/groupId/artifactId/api/current.txt b/development/project-creator/compose-template/groupId/artifactId/api/current.txt deleted file mode 100644 index e6f50d0d0fd11..0000000000000 --- a/development/project-creator/compose-template/groupId/artifactId/api/current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 4.0 diff --git a/development/project-creator/compose-template/groupId/artifactId/build.gradle b/development/project-creator/compose-template/groupId/artifactId/build.gradle deleted file mode 100644 index 68530fd433db8..0000000000000 --- a/development/project-creator/compose-template/groupId/artifactId/build.gradle +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file was created using the `create_project.py` script located in the - * `/development/project-creator` directory. - * - * Please use that script when creating a new project, rather than copying an existing project and - * modifying its settings. - */ - -import androidx.build.SoftwareType -import androidx.build.PlatformIdentifier - -plugins { - id("AndroidXPlugin") - id("AndroidXComposePlugin") -} - -androidXMultiplatform { - androidLibrary { - namespace = "" - - } - jvmStubs() - linuxX64Stubs() - - defaultPlatform(PlatformIdentifier.ANDROID) - - sourceSets { - commonMain { - dependencies { - } - } - - commonTest { - dependencies { - } - } - - jvmMain { - dependsOn(commonMain) - dependencies { - implementation(libs.testRules) - implementation(libs.testRunner) - implementation(libs.junit) - implementation(libs.truth) - } - } - - androidMain { - dependsOn(jvmMain) - dependencies { - api("androidx.annotation:annotation:1.8.1") - } - } - - jvmTest { - dependsOn(commonTest) - dependencies { - } - } - - androidDeviceTest { - dependsOn(jvmTest) - dependencies { - implementation(libs.testRules) - implementation(libs.testRunner) - implementation(libs.junit) - implementation(libs.truth) - } - } - - commonStubsMain { - dependsOn(commonMain) - } - - jvmStubsMain { - dependsOn(commonStubsMain) - } - - linuxx64StubsMain { - dependsOn(commonStubsMain) - } - } -} - -androidx { - name = "" - type = SoftwareType. - mavenVersion = LibraryVersions. - inceptionYear = "" - description = "" -} diff --git a/development/project-creator/compose-template/groupId/artifactId/src/commonMain/kotlin/groupId/artifactId-documentation.md b/development/project-creator/compose-template/groupId/artifactId/src/commonMain/kotlin/groupId/artifactId-documentation.md deleted file mode 100644 index a37b120e17182..0000000000000 --- a/development/project-creator/compose-template/groupId/artifactId/src/commonMain/kotlin/groupId/artifactId-documentation.md +++ /dev/null @@ -1,7 +0,0 @@ -# Module root - - - -# Package - -Insert package level documentation here diff --git a/development/project-creator/create_project.py b/development/project-creator/create_project.py deleted file mode 100755 index da2c6abd0b136..0000000000000 --- a/development/project-creator/create_project.py +++ /dev/null @@ -1,708 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import sys -import os -import argparse -from datetime import date -import subprocess -from enum import Enum -from textwrap import dedent -from shutil import rmtree -from shutil import copyfile -from shutil import copytree -import re - -try: - # non-default python3 module, be helpful if it is missing - import toml -except ModuleNotFoundError as e: - print(e) - print("Consider running `pip install toml` to install this module") - exit(-1) - -# cd into directory of script -os.chdir(os.path.dirname(os.path.abspath(__file__))) - -FRAMEWORKS_SUPPORT_FP = os.path.abspath(os.path.join(os.getcwd(), '..', '..')) -SAMPLE_OWNERS_FP = os.path.abspath(os.path.join(os.getcwd(), 'kotlin-template', 'groupId', 'OWNERS')) -SAMPLE_JAVA_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'java-template', 'groupId', 'artifactId')) -SAMPLE_KOTLIN_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'kotlin-template', 'groupId', 'artifactId')) -SAMPLE_COMPOSE_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'compose-template', 'groupId', 'artifactId')) -NATIVE_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'native-template', 'groupId', 'artifactId')) -SETTINGS_GRADLE_FP = os.path.abspath(os.path.join(os.getcwd(), '..', '..', "settings.gradle")) -LIBRARY_VERSIONS_REL = './libraryversions.toml' -LIBRARY_VERSIONS_FP = os.path.join(FRAMEWORKS_SUPPORT_FP, LIBRARY_VERSIONS_REL) -DOCS_TOT_BUILD_GRADLE_REL = './docs-tip-of-tree/build.gradle' -DOCS_TOT_BUILD_GRADLE_FP = os.path.join(FRAMEWORKS_SUPPORT_FP, DOCS_TOT_BUILD_GRADLE_REL) - -# Set up input arguments -parser = argparse.ArgumentParser( - description=("""Genereates new project in androidx.""")) -parser.add_argument( - 'group_id', - help='group_id for the new library') -parser.add_argument( - 'artifact_id', - help='artifact_id for the new library') - - -class ProjectType(Enum): - KOTLIN = 0 - JAVA = 1 - NATIVE = 2 - -def print_e(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - -def cp(src_path_dir, dst_path_dir): - """Copies all files in the src_path_dir into the dst_path_dir - - Args: - src_path_dir: the source directory, which must exist - dst_path_dir: the distination directory - """ - if not os.path.exists(dst_path_dir): - os.makedirs(dst_path_dir) - if not os.path.exists(src_path_dir): - print_e('cp error: Source path %s does not exist.' % src_path_dir) - return None - try: - copytree(src_path_dir, dst_path_dir, dirs_exist_ok=True) - except Error as err: - print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir)) - return None - return dst_path_dir - -def rm(path): - if os.path.isdir(path): - rmtree(path) - elif os.path.exists(path): - os.remove(path) - -def mv_dir(src_path_dir, dst_path_dir): - """Moves a directory from src_path_dir to dst_path_dir. - - Args: - src_path_dir: the source directory, which must exist - dst_path_dir: the distination directory - """ - if os.path.exists(dst_path_dir): - print_e('rename error: Destination path %s already exists.' % dst_path_dir) - return None - # If moving to a new parent directory, create that directory - parent_dst_path_dir = os.path.dirname(dst_path_dir) - if not os.path.exists(parent_dst_path_dir): - os.makedirs(parent_dst_path_dir) - if not os.path.exists(src_path_dir): - print_e('mv error: Source path %s does not exist.' % src_path_dir) - return None - try: - os.rename(src_path_dir, dst_path_dir) - except OSError as error: - print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir)) - print_e(error) - return None - return dst_path_dir - -def rename_file(src_file, new_file_name): - """Renames a file from src_file to new_file_name, within the same directory. - - Args: - src_file: the source file, which must exist - new_file_name: the new file name - """ - if not os.path.exists(src_file): - print_e('mv file error: Source file %s does not exist.' % src_file) - return None - # Check that destination directory already exists - parent_src_file_dir = os.path.dirname(src_file) - new_file_path = os.path.join(parent_src_file_dir, new_file_name) - if os.path.exists(new_file_path): - print_e('mv file error: Source file %s already exists.' % new_file_path) - return None - try: - os.rename(src_file, new_file_path) - except OSError as error: - print_e('FAIL: Unable to rename %s to destination %s' % (src_file, new_file_path)) - print_e(error) - return None - return new_file_path - -def create_file(path): - """ - Creates an empty file if it does not already exist. - """ - open(path, "a").close() - -def generate_package_name(group_id, artifact_id): - final_group_id_word = group_id.split(".")[-1] - artifact_id_suffix = re.sub(r"\b%s\b" % final_group_id_word, "", artifact_id) - artifact_id_suffix = artifact_id_suffix.replace("-", ".") - if (final_group_id_word == artifact_id): - return group_id + artifact_id_suffix - elif (final_group_id_word != artifact_id): - if ("." in artifact_id_suffix): - return group_id + artifact_id_suffix - else: - return group_id + "." + artifact_id_suffix - -def validate_name(group_id, artifact_id): - if not group_id.startswith("androidx."): - print_e("Group ID must start with androidx.") - return False - final_group_id_word = group_id.split(".")[-1] - if not artifact_id.startswith(final_group_id_word): - print_e("Artifact ID must use the final word in the group Id " + \ - "as the prefix. For example, `androidx.foo.bar:bar-qux`" + \ - "or `androidx.foo:foo-bar` are valid names.") - return False - return True - -def get_year(): - return str(date.today().year) - -def get_group_id_version_macro(group_id): - group_id_version_macro = group_id.replace("androidx.", "").replace(".", "_").upper() - if group_id == "androidx.compose": - group_id_version_macro = "COMPOSE" - elif group_id.startswith("androidx.compose"): - group_id_version_macro = group_id.replace("androidx.compose.", "").replace(".", - "_").upper() - return group_id_version_macro - -def sed(before, after, file): - with open(file) as f: - file_contents = f.read() - new_file_contents = file_contents.replace(before, after) - # write back the file - with open(file,"w") as f: - f.write(new_file_contents) - -def remove_line(line_to_remove, file): - with open(file) as f: - file_contents = f.readlines() - new_file_contents = [] - for line in file_contents: - if line_to_remove not in line: - new_file_contents.append(line) - # write back the file - with open(file,"w") as f: - f.write("".join(new_file_contents)) - -def ask_yes_or_no(question): - while(True): - reply = str(input(question+' (y/n): ')).lower().strip() - if reply: - if reply[0] == 'y': return True - if reply[0] == 'n': return False - print("Please respond with y/n") - -def ask_project_type(): - """Asks the user which type of project they wish to create""" - message = dedent(""" - Please choose the type of project you would like to create: - 1: Kotlin (AAR) - 2: Java (AAR / JAR) - 3: Native (AAR) - """).strip() - while(True): - reply = str(input(message + "\n")).strip() - if reply == "1": return ProjectType.KOTLIN - if reply == "2": - if confirm_java_project_type(): - return ProjectType.JAVA - if reply == "3": return ProjectType.NATIVE - print("Please respond with one of the presented options") - -def confirm_java_project_type(): - return ask_yes_or_no("All new androidx projects are expected and encouraged " - "to use Kotlin. Java projects should only be used if " - "there is a business need to do so. " - "Please ack to proceed:") - -def ask_library_purpose(): - question = ("Project description (please complete the sentence): " - "This library makes it easy for developers to... ") - while(True): - reply = str(input(question)).strip() - if reply: return reply - print("Please input a description!") - -def ask_project_description(): - question = ("Please provide a project description: ") - while(True): - reply = str(input(question)).strip() - if reply: return reply - print("Please input a description!") - -def get_gradle_project_coordinates(group_id, artifact_id): - coordinates = group_id.replace("androidx", "").replace(".",":") - coordinates += ":" + artifact_id - return coordinates - -def run_update_api(group_id, artifact_id): - gradle_coordinates = get_gradle_project_coordinates(group_id, artifact_id) - gradle_cmd = "cd " + FRAMEWORKS_SUPPORT_FP + " && ./gradlew " + gradle_coordinates + ":updateApi" - try: - subprocess.check_output(gradle_cmd, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError: - print_e('FAIL: Unable run updateApi with command: %s' % gradle_cmd) - return None - return True - -def get_library_type(artifact_id): - """Returns the appropriate androidx.build.SoftwareType for the project. - """ - if "sample" in artifact_id: - library_type = "SAMPLES" - elif "compiler" in artifact_id: - library_type = "ANNOTATION_PROCESSOR" - elif "lint" in artifact_id: - library_type = "LINT" - elif "inspection" in artifact_id: - library_type = "IDE_PLUGIN" - else: - library_type = "PUBLISHED_LIBRARY" - return library_type - -def get_group_id_path(group_id): - """Generates the group ID filepath - - Given androidx.foo.bar, the structure will be: - frameworks/support/foo/bar - - Args: - group_id: group_id of the new library - """ - return FRAMEWORKS_SUPPORT_FP + "/" + group_id.replace("androidx.", "").replace(".", "/") - -def get_full_artifact_path(group_id, artifact_id): - """Generates the full artifact ID filepath - - Given androidx.foo.bar:bar-qux, the structure will be: - frameworks/support/foo/bar/bar-qux - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - group_id_path = get_group_id_path(group_id) - return group_id_path + "/" + artifact_id - -def get_package_documentation_file_dir(group_id, artifact_id): - """Generates the full package documentation directory - - Given androidx.foo.bar:bar-qux, the structure will be: - frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/package-info.java - - For Kotlin: - frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/--documentation.md - - For Compose: - frameworks/support/foo/bar/bar-qux/src/commonMain/kotlin/androidx/foo/--documentation.md - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - full_artifact_path = get_full_artifact_path(group_id, artifact_id) - if "compose" in group_id: - group_id_subpath = "/src/commonMain/kotlin/" + \ - group_id.replace(".", "/") - else: - group_id_subpath = "/src/main/java/" + \ - group_id.replace(".", "/") - return full_artifact_path + group_id_subpath - -def get_package_documentation_filename(group_id, artifact_id, project_type): - """Generates the documentation filename - - Given androidx.foo.bar:bar-qux, the structure will be: - package-info.java - - or for Kotlin: - --documentation.md - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - is_kotlin_project: whether or not the library is a kotin project - """ - if project_type == ProjectType.JAVA: - return "package-info.java" - else: - formatted_group_id = group_id.replace(".", "-") - return "%s-%s-documentation.md" % (formatted_group_id, artifact_id) - -def is_compose_project(group_id, artifact_id): - """Returns true if project can be inferred to be a compose / Kotlin project - """ - return "compose" in group_id or "compose" in artifact_id - -def create_directories(group_id, artifact_id, project_type, is_compose_project): - """Creates the standard directories for the given group_id and artifact_id. - - Given androidx.foo.bar:bar-qux, the structure will be: - frameworks/support/foo/bar/bar-qux/build.gradle - frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/bar/package-info.java - frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/bar/artifact-documentation.md - frameworks/support/foo/bar/bar-qux/api/current.txt - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - full_artifact_path = get_full_artifact_path(group_id, artifact_id) - if not os.path.exists(full_artifact_path): - os.makedirs(full_artifact_path) - - # Copy over the OWNERS file if it doesn't exit - group_id_path = get_group_id_path(group_id) - if not os.path.exists(group_id_path + "/OWNERS"): - copyfile(SAMPLE_OWNERS_FP, group_id_path + "/OWNERS") - - # Copy the full src structure, depending on the project source code - if is_compose_project: - print("Auto-detected Compose project.") - cp(SAMPLE_COMPOSE_SRC_FP, full_artifact_path) - elif project_type == ProjectType.NATIVE: - cp(NATIVE_SRC_FP, full_artifact_path) - elif project_type == ProjectType.KOTLIN: - cp(SAMPLE_KOTLIN_SRC_FP, full_artifact_path) - else: - cp(SAMPLE_JAVA_SRC_FP, full_artifact_path) - - # Populate the library type - library_type = get_library_type(artifact_id) - - # If it's a sample project, remove the api directory - if library_type == "SAMPLES": - api_dir_path = os.path.join(full_artifact_path, "api") - if os.path.exists(api_dir_path): - rm(api_dir_path) - # Java only libraries have no dependency on android. - # Java-only produces a jar, whereas an android library produces an aar. - if (project_type == ProjectType.JAVA and - (get_library_type(artifact_id) == "LINT" or - ask_yes_or_no("Is this a java-only library? Java-only libraries produce" - " JARs, whereas Android libraries produce AARs."))): - sed("com.android.library", "java-library", - full_artifact_path + "/build.gradle") - sed("org.jetbrains.kotlin.android", "kotlin", - full_artifact_path + "/build.gradle") - - # Atomic group Ids have their version configured automatically, - # so we can remove the version line from the build file. - if is_group_id_atomic(group_id): - remove_line("mavenVersion = LibraryVersions.", - full_artifact_path + "/build.gradle") - - # If the project is a library that produces a jar/aar that will go - # on GMaven, ask for a special project description. - if get_library_type(artifact_id) == "PUBLISHED_LIBRARY": - project_description = ask_library_purpose() - else: - project_description = ask_project_description() - - # Set up the package documentation. - full_package_docs_dir = get_package_documentation_file_dir(group_id, artifact_id) - package_docs_filename = get_package_documentation_filename(group_id, artifact_id, project_type) - full_package_docs_file = os.path.join(full_package_docs_dir, package_docs_filename) - # Compose projects use multiple main directories, so we handle it separately - if is_compose_project: - # Kotlin projects use -documentation.md files, so we need to rename it appropriately. - rename_file(full_artifact_path + "/src/commonMain/kotlin/groupId/artifactId-documentation.md", - package_docs_filename) - mv_dir(full_artifact_path + "/src/commonMain/kotlin/groupId", full_package_docs_dir) - else: - if project_type != ProjectType.JAVA: - # Kotlin projects use -documentation.md files, so we need to rename it appropriately. - # We also rename this file for native projects in case they also have public Kotlin APIs - rename_file(full_artifact_path + "/src/main/java/groupId/artifactId-documentation.md", - package_docs_filename) - mv_dir(full_artifact_path + "/src/main/java/groupId", full_package_docs_dir) - - if project_type == ProjectType.NATIVE and library_type == "PUBLISHED_LIBRARY": - library_type = "PUBLISHED_NATIVE_LIBRARY" - sed("", library_type, full_artifact_path + "/build.gradle") - - # Populate the YEAR - year = get_year() - sed("", year, full_artifact_path + "/build.gradle") - sed("", year, full_package_docs_file) - - # Populate the PACKAGE - package = generate_package_name(group_id, artifact_id) - sed("", package, full_package_docs_file) - sed("", package, full_artifact_path + "/build.gradle") - - # Populate the VERSION macro - group_id_version_macro = get_group_id_version_macro(group_id) - sed("", group_id_version_macro, full_artifact_path + "/build.gradle") - # Update the name and description in the build.gradle - sed("", group_id + ":" + artifact_id, full_artifact_path + "/build.gradle") - if project_type == ProjectType.NATIVE: - sed("", artifact_id, full_artifact_path + "/src/main/cpp/CMakeLists.txt") - sed("", artifact_id, full_artifact_path + "/build.gradle") - create_file(full_artifact_path + "/src/main/cpp/" + artifact_id + ".cpp") - sed("", project_description, full_artifact_path + "/build.gradle") - - -def get_new_settings_gradle_line(group_id, artifact_id): - """Generates the line needed for frameworks/support/settings.gradle. - - For a library androidx.foo.bar:bar-qux, the new gradle command will be - the form: - ./gradlew :foo:bar:bar-qux: - - We special case on compose that we can properly populate the build type - of either MAIN or COMPOSE. - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - - build_type = "MAIN" - if is_compose_project(group_id, artifact_id): - build_type = "COMPOSE" - - gradle_cmd = get_gradle_project_coordinates(group_id, artifact_id) - return "includeProject(\"" + gradle_cmd + "\", [BuildType." + build_type + "])\n" - -def update_settings_gradle(group_id, artifact_id): - """Updates frameworks/support/settings.gradle with the new library. - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - # Open file for reading and get all lines - with open(SETTINGS_GRADLE_FP, 'r') as f: - settings_gradle_lines = f.readlines() - num_lines = len(settings_gradle_lines) - - new_settings_gradle_line = get_new_settings_gradle_line(group_id, artifact_id) - for i in range(num_lines): - cur_line = settings_gradle_lines[i] - if "includeProject" not in cur_line: - continue - # Iterate through until you found the alphabetical place to insert the new line - if new_settings_gradle_line <= cur_line: - insert_line = i - break - else: - insert_line = i + 1 - settings_gradle_lines.insert(insert_line, new_settings_gradle_line) - - # Open file for writing and update all lines - with open(SETTINGS_GRADLE_FP, 'w') as f: - f.writelines(settings_gradle_lines) - -def get_new_docs_tip_of_tree_build_grade_line(group_id, artifact_id): - """Generates the line needed for docs-tip-of-tree/build.gradle. - - For a library androidx.foo.bar:bar-qux, the new line will be of the form: - docs(project(":foo:bar:bar-qux")) - - If it is a sample project, then it will return None. samples(project(":foo:bar:bar-qux-sample")) needs to be added to the androidx block of the library build.gradle file. - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - - gradle_cmd = get_gradle_project_coordinates(group_id, artifact_id) - prefix = "docs" - if "sample" in gradle_cmd: - print("Auto-detected sample project. Please add the sample dependency to androidx block of the library build.gradle file. See compose/ui/ui/build.gradle for an example.") - return None - return " %s(project(\"%s\"))\n" % (prefix, gradle_cmd) - -def update_docs_tip_of_tree_build_grade(group_id, artifact_id): - """Updates docs-tip-of-tree/build.gradle with the new library. - - We ask for confirmation if the library contains either "benchmark" - or "test". - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - # Confirm with user that we want to generate docs for anything - # that might be a test or a benchmark. - if ("test" in group_id or "test" in artifact_id - or "benchmark" in group_id or "benchmark" in artifact_id): - if not ask_yes_or_no(("Should tip-of-tree documentation be generated " - "for project %s:%s?" % (group_id, artifact_id))): - return - - # Open file for reading and get all lines - with open(DOCS_TOT_BUILD_GRADLE_FP, 'r') as f: - docs_tot_bg_lines = f.readlines() - index_of_real_dependencies_block = next( - idx for idx, line in enumerate(docs_tot_bg_lines) if line.startswith("dependencies {") - ) - if (index_of_real_dependencies_block == None): - raise RuntimeError("Couldn't find dependencies block") - num_lines = len(docs_tot_bg_lines) - - new_docs_tot_bq_line = get_new_docs_tip_of_tree_build_grade_line(group_id, artifact_id) - for i in range(index_of_real_dependencies_block, num_lines): - cur_line = docs_tot_bg_lines[i] - if "project" not in cur_line: - continue - if new_docs_tot_bq_line == None: - return - # Iterate through until you found the alphabetical place to insert the new line - if new_docs_tot_bq_line.split("project")[1] <= cur_line.split("project")[1]: - insert_line = i - break - else: - insert_line = i + 1 - docs_tot_bg_lines.insert(insert_line, new_docs_tot_bq_line) - - # Open file for writing and update all lines - with open(DOCS_TOT_BUILD_GRADLE_FP, 'w') as f: - f.writelines(docs_tot_bg_lines) - - -def insert_new_group_id_into_library_versions_toml(group_id): - """Inserts a group ID into the libraryversions.toml file. - - If one already exists, then this function just returns and reuses - the existing one. - - Args: - group_id: group_id of the new library - """ - new_group_id_variable_name = group_id.replace("androidx.","").replace(".","_").upper() - - # Open toml file - library_versions = toml.load(LIBRARY_VERSIONS_FP, decoder=toml.TomlPreserveCommentDecoder()) - if not new_group_id_variable_name in library_versions["versions"]: - library_versions["versions"][new_group_id_variable_name] = "1.0.0-alpha01" - if not new_group_id_variable_name in library_versions["groups"]: - decoder = toml.decoder.TomlDecoder() - group_entry = decoder.get_empty_inline_table() - group_entry["group"] = group_id - group_entry["atomicGroupVersion"] = "versions." + new_group_id_variable_name - library_versions["groups"][new_group_id_variable_name] = group_entry - - # Sort the entries - library_versions["versions"] = dict(sorted(library_versions["versions"].items())) - library_versions["groups"] = dict(sorted(library_versions["groups"].items())) - - # Open file for writing and update toml - with open(LIBRARY_VERSIONS_FP, 'w') as f: - # Encoder arg enables preservation of inline dicts. - versions_toml_file_string = toml.dumps(library_versions, - encoder=toml.TomlPreserveCommentEncoder(preserve=True)) - versions_toml_file_string_new = re.sub(",]", " ]", versions_toml_file_string) - versions_toml_file_string_new - f.write(versions_toml_file_string_new) - - -def is_group_id_atomic(group_id): - """Checks if a group ID is atomic using the libraryversions.toml file. - - If one already exists, then this function evaluates the group id - and returns the appropriate atomicity. Otherwise, it returns - False. - - Example of an atomic library group: - ACTIVITY = { group = "androidx.work", atomicGroupVersion = "WORK" } - Example of a non-atomic library group: - WEAR = { group = "androidx.wear" } - - Args: - group_id: group_id of the library we're checking. - """ - library_versions = toml.load(LIBRARY_VERSIONS_FP) - for library_group in library_versions["groups"]: - if group_id == library_versions["groups"][library_group]["group"]: - return "atomicGroupVersion" in library_versions["groups"][library_group] - - return False - - -def print_todo_list(group_id, artifact_id, project_type): - """Prints to the todo list once the script has finished. - - There are some pieces that can not be automated or require human eyes. - List out the appropriate todos so that the users knows what needs - to be done prior to uploading. - - Args: - group_id: group_id of the new library - artifact_id: group_id of the new library - """ - build_gradle_path = get_full_artifact_path(group_id, artifact_id) + \ - "/build.gradle" - owners_file_path = get_group_id_path(group_id) + "/OWNERS" - package_docs_path = os.path.join( - get_package_documentation_file_dir(group_id, artifact_id), - get_package_documentation_filename(group_id, artifact_id, project_type)) - print("---\n") - print("Created the project. The following TODOs need to be completed by " - "you:\n") - print("\t1. Check that the OWNERS file is in the correct place. It is " - "currently at:" - "\n\t\t" + owners_file_path) - print("\t2. Add your name (and others) to the OWNERS file:" + \ - "\n\t\t" + owners_file_path) - print("\t3. Check that the correct library version is assigned in the " - "build.gradle:" - "\n\t\t" + build_gradle_path) - print("\t4. Fill out the project/module name in the build.gradle:" - "\n\t\t" + build_gradle_path) - print("\t5. Update the project/module package documentation:" - "\n\t\t" + package_docs_path) - -def main(args): - # Parse arguments and check for existence of build ID or file - args = parser.parse_args() - if not args.group_id or not args.artifact_id: - parser.error("You must specify a group_id and an artifact_id") - sys.exit(1) - if not validate_name(args.group_id, args.artifact_id): - sys.exit(1) - if is_compose_project(args.group_id, args.artifact_id): - project_type = ProjectType.KOTLIN - else: - project_type = ask_project_type() - insert_new_group_id_into_library_versions_toml( - args.group_id - ) - create_directories( - args.group_id, - args.artifact_id, - project_type, - is_compose_project(args.group_id, args.artifact_id) - ) - update_settings_gradle(args.group_id, args.artifact_id) - update_docs_tip_of_tree_build_grade(args.group_id, args.artifact_id) - print("Created directories. \nRunning updateApi for the new " - "library, this may take a minute...", end='') - if run_update_api(args.group_id, args.artifact_id): - print("done.") - else: - print("failed. Please investigate manually.") - print_todo_list(args.group_id, args.artifact_id, project_type) - -if __name__ == '__main__': - main(sys.argv) diff --git a/development/project-creator/create_project.sh b/development/project-creator/create_project.sh deleted file mode 100755 index d976d21a4f70b..0000000000000 --- a/development/project-creator/create_project.sh +++ /dev/null @@ -1,27 +0,0 @@ -SCRIPT_DIR="$(cd $(dirname $0) && pwd)" - -if [ "$(uname)" = "Darwin" ]; then - VIRTUAL_ENV_INSTALL_COMMAND="pip3 install --require-hashes -r base-requirements.txt" - else - VIRTUAL_ENV_INSTALL_COMMAND="sudo apt-get install virtualenv python3-venv" -fi - - -# check if virtualenv is installed -if !(pyenv_version=$(virtualenv --version > /dev/null 2>&1)); then - echo "virtualenv is not installed. Please install with '$VIRTUAL_ENV_INSTALL_COMMAND'" - exit 1 -fi - -# create virtualenv -virtualenv androidx_project_creator - -# install necessary tools -androidx_project_creator/bin/pip3 install --require-hashes -r $SCRIPT_DIR/requirements.txt - -# run project creator -androidx_project_creator/bin/python3 $SCRIPT_DIR/create_project.py "$@" - - -# clean up virtualenv directory -rm -rf ./androidx-project_creator diff --git a/development/project-creator/java-template/groupId/OWNERS b/development/project-creator/java-template/groupId/OWNERS deleted file mode 100644 index 15331e5f84a15..0000000000000 --- a/development/project-creator/java-template/groupId/OWNERS +++ /dev/null @@ -1 +0,0 @@ -# example@google.com diff --git a/development/project-creator/java-template/groupId/artifactId/api/current.txt b/development/project-creator/java-template/groupId/artifactId/api/current.txt deleted file mode 100644 index e6f50d0d0fd11..0000000000000 --- a/development/project-creator/java-template/groupId/artifactId/api/current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 4.0 diff --git a/development/project-creator/java-template/groupId/artifactId/build.gradle b/development/project-creator/java-template/groupId/artifactId/build.gradle deleted file mode 100644 index 975c2293180c7..0000000000000 --- a/development/project-creator/java-template/groupId/artifactId/build.gradle +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file was created using the `create_project.py` script located in the - * `/development/project-creator` directory. - * - * Please use that script when creating a new project, rather than copying an existing project and - * modifying its settings. - */ -import androidx.build.SoftwareType - -plugins { - id("AndroidXPlugin") - id("com.android.library") -} - -dependencies { - annotationProcessor(libs.nullaway) - // Add dependencies here -} - -android { - namespace = "" -} - -androidx { - name = "" - type = SoftwareType. - mavenVersion = LibraryVersions. - inceptionYear = "" - description = "" -} diff --git a/development/project-creator/kotlin-template/groupId/OWNERS b/development/project-creator/kotlin-template/groupId/OWNERS deleted file mode 100644 index 15331e5f84a15..0000000000000 --- a/development/project-creator/kotlin-template/groupId/OWNERS +++ /dev/null @@ -1 +0,0 @@ -# example@google.com diff --git a/development/project-creator/kotlin-template/groupId/artifactId/api/current.txt b/development/project-creator/kotlin-template/groupId/artifactId/api/current.txt deleted file mode 100644 index e6f50d0d0fd11..0000000000000 --- a/development/project-creator/kotlin-template/groupId/artifactId/api/current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 4.0 diff --git a/development/project-creator/kotlin-template/groupId/artifactId/build.gradle b/development/project-creator/kotlin-template/groupId/artifactId/build.gradle deleted file mode 100644 index 56b830eeb1a50..0000000000000 --- a/development/project-creator/kotlin-template/groupId/artifactId/build.gradle +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file was created using the `create_project.py` script located in the - * `/development/project-creator` directory. - * - * Please use that script when creating a new project, rather than copying an existing project and - * modifying its settings. - */ -import androidx.build.SoftwareType - -plugins { - id("AndroidXPlugin") - id("com.android.library") -} - -dependencies { - // Add dependencies here -} - -android { - namespace = "" -} - -androidx { - name = "" - type = SoftwareType. - mavenVersion = LibraryVersions. - inceptionYear = "" - description = "" -} diff --git a/development/project-creator/kotlin-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md b/development/project-creator/kotlin-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md deleted file mode 100644 index a37b120e17182..0000000000000 --- a/development/project-creator/kotlin-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md +++ /dev/null @@ -1,7 +0,0 @@ -# Module root - - - -# Package - -Insert package level documentation here diff --git a/development/project-creator/native-template/groupId/OWNERS b/development/project-creator/native-template/groupId/OWNERS deleted file mode 100644 index 15331e5f84a15..0000000000000 --- a/development/project-creator/native-template/groupId/OWNERS +++ /dev/null @@ -1 +0,0 @@ -# example@google.com diff --git a/development/project-creator/native-template/groupId/artifactId/api/current.txt b/development/project-creator/native-template/groupId/artifactId/api/current.txt deleted file mode 100644 index e6f50d0d0fd11..0000000000000 --- a/development/project-creator/native-template/groupId/artifactId/api/current.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 4.0 diff --git a/development/project-creator/native-template/groupId/artifactId/build.gradle b/development/project-creator/native-template/groupId/artifactId/build.gradle deleted file mode 100644 index a118661d01917..0000000000000 --- a/development/project-creator/native-template/groupId/artifactId/build.gradle +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file was created using the `create_project.py` script located in the - * `/development/project-creator` directory. - * - * Please use that script when creating a new project, rather than copying an existing project and - * modifying its settings. - */ -import androidx.build.SoftwareType - -plugins { - id("AndroidXPlugin") - id("com.android.library") -} - -dependencies { - // Add dependencies here -} - -androidx { - name = "" - type = SoftwareType. - mavenVersion = LibraryVersions. - inceptionYear = "" - description = "" -} - -android { - namespace = "" - defaultConfig { - externalNativeBuild { - cmake { - arguments "-DANDROID_STL=c++_shared" - targets "" - } - } - } - externalNativeBuild { - cmake { - version = libs.versions.cmake.get() - path "src/main/cpp/CMakeLists.txt" - } - } - - buildFeatures { - prefabPublishing true - } - - prefab { - { - headers "src/main/cpp/include" - } - } -} \ No newline at end of file diff --git a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt b/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt deleted file mode 100644 index c1ae2b9ddf136..0000000000000 --- a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.22.1) - -project( LANGUAGES CXX) - -add_library( - SHARED - .cpp) - -set_property(TARGET - APPEND_STRING PROPERTY - LINK_FLAGS - " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version_scripts/libname.map.txt") - -target_link_options( PRIVATE "-Wl,-z,max-page-size=16384") diff --git a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/include/.keep b/development/project-creator/native-template/groupId/artifactId/src/main/cpp/include/.keep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/version_scripts/libname.map.txt b/development/project-creator/native-template/groupId/artifactId/src/main/cpp/version_scripts/libname.map.txt deleted file mode 100644 index fe39d9e8e1ef2..0000000000000 --- a/development/project-creator/native-template/groupId/artifactId/src/main/cpp/version_scripts/libname.map.txt +++ /dev/null @@ -1,4 +0,0 @@ -LIBNAME { - local: - *; -}; \ No newline at end of file diff --git a/development/project-creator/native-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md b/development/project-creator/native-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md deleted file mode 100644 index a37b120e17182..0000000000000 --- a/development/project-creator/native-template/groupId/artifactId/src/main/java/groupId/artifactId-documentation.md +++ /dev/null @@ -1,7 +0,0 @@ -# Module root - - - -# Package - -Insert package level documentation here diff --git a/development/project-creator/requirements.txt b/development/project-creator/requirements.txt deleted file mode 100644 index bfdc60517646d..0000000000000 --- a/development/project-creator/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -setuptools==78.1.1 --hash=sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561 -toml==0.10.2 --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b diff --git a/development/project-creator/test_project_creator.py b/development/project-creator/test_project_creator.py deleted file mode 100755 index bcc62bb2137dc..0000000000000 --- a/development/project-creator/test_project_creator.py +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import unittest -import os -from create_project import * - -class TestNewDirectory(unittest.TestCase): - - def test_package_name(self): - package = generate_package_name("androidx.foo", "foo") - self.assertEqual("androidx.foo", package) - - package = generate_package_name("androidx.foo", "foo-bar") - self.assertEqual("androidx.foo.bar", package) - - package = generate_package_name("androidx.foo.bar", "bar") - self.assertEqual("androidx.foo.bar", package) - - package = generate_package_name("androidx.foo.bar", "bar-qux") - self.assertEqual("androidx.foo.bar.qux", package) - - def test_name_correctness(self): - self.assertFalse(validate_name("foo", "bar")) - self.assertFalse(validate_name("foo", "foo")) - self.assertFalse(validate_name("androidx.foo", "bar")) - self.assertFalse(validate_name("androidx.foo", "bar-qux")) - self.assertFalse(validate_name("androidx.foo.bar", "foo")) - self.assertTrue(validate_name("androidx.foo", "foo")) - self.assertTrue(validate_name("androidx.foo", "foo-bar")) - self.assertTrue(validate_name("androidx.foo.bar", "bar")) - self.assertTrue(validate_name("androidx.foo.bar", "bar-qux")) - - def test_full_directory_name(self): - full_fp = get_full_artifact_path("androidx.foo", "foo") - self.assertTrue(full_fp.endswith("frameworks/support/foo/foo")) - - full_fp = get_full_artifact_path("androidx.foo", "foo-bar") - self.assertTrue(full_fp.endswith("frameworks/support/foo/foo-bar")) - - full_fp = get_full_artifact_path("androidx.foo.bar", "bar") - self.assertTrue(full_fp.endswith("frameworks/support/foo/bar/bar")) - - full_fp = get_full_artifact_path("androidx.foo.bar", "bar-qux") - self.assertTrue(full_fp.endswith("frameworks/support/foo/bar/bar-qux")) - - def test_get_package_documentation_file_dir(self): - package_info_dir_fp = get_package_documentation_file_dir("androidx.foo", "foo") - frameworks_support_fp = os.path.abspath(os.path.join(os.getcwd(), '..', '..')) - self.assertEqual(frameworks_support_fp + "/foo/foo/src/main/java/androidx/foo", package_info_dir_fp) - - package_info_dir_fp = get_package_documentation_file_dir("androidx.foo", "foo-bar") - self.assertEqual(frameworks_support_fp + "/foo/foo-bar/src/main/java/androidx/foo", package_info_dir_fp) - - package_info_dir_fp = get_package_documentation_file_dir("androidx.foo.bar", "bar") - self.assertEqual(frameworks_support_fp + "/foo/bar/bar/src/main/java/androidx/foo/bar", package_info_dir_fp) - - package_info_dir_fp = get_package_documentation_file_dir("androidx.foo.bar", "bar-qux") - self.assertEqual(frameworks_support_fp + "/foo/bar/bar-qux/src/main/java/androidx/foo/bar", package_info_dir_fp) - - package_info_dir_fp = get_package_documentation_file_dir("androidx.compose.bar", "bar-qux") - self.assertEqual(frameworks_support_fp + "/compose/bar/bar-qux/src/commonMain/kotlin/androidx/compose/bar", - package_info_dir_fp) - - package_info_dir_fp = get_package_documentation_file_dir("androidx.foo.compose", "compose-qux") - self.assertEqual(frameworks_support_fp + "/foo/compose/compose-qux/src/commonMain/kotlin/androidx/foo/compose", - package_info_dir_fp) - - def test_get_package_documentation_filename(self): - frameworks_support_fp = os.path.abspath(os.path.join(os.getcwd(), '..', '..')) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo", "foo", ProjectType.KOTLIN) - self.assertEqual("androidx-foo-foo-documentation.md", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo", "foo", ProjectType.NATIVE) - self.assertEqual("androidx-foo-foo-documentation.md", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo", "foo-bar", ProjectType.KOTLIN) - self.assertEqual("androidx-foo-foo-bar-documentation.md", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo.bar", "bar", ProjectType.KOTLIN) - self.assertEqual("androidx-foo-bar-bar-documentation.md", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo.bar", "bar-qux", ProjectType.KOTLIN) - self.assertEqual("androidx-foo-bar-bar-qux-documentation.md", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo", "foo", ProjectType.JAVA) - self.assertEqual("package-info.java", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo", "foo-bar", ProjectType.JAVA) - self.assertEqual("package-info.java", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo.bar", "bar", ProjectType.JAVA) - self.assertEqual("package-info.java", package_info_dir_filename) - - package_info_dir_filename = get_package_documentation_filename("androidx.foo.bar", "bar-qux", ProjectType.JAVA) - self.assertEqual("package-info.java", package_info_dir_filename) - - def test_group_id_directory_name(self): - full_fp = get_group_id_path("androidx.foo") - self.assertTrue(full_fp.endswith("frameworks/support/foo")) - - full_fp = get_group_id_path("androidx.foo") - self.assertTrue(full_fp.endswith("frameworks/support/foo")) - - full_fp = get_group_id_path("androidx.foo.bar") - self.assertTrue(full_fp.endswith("frameworks/support/foo/bar")) - - full_fp = get_group_id_path("androidx.foo.bar") - self.assertTrue(full_fp.endswith("frameworks/support/foo/bar")) - - -class TestSettingsGradle(unittest.TestCase): - - def test_settings_gradle_line(self): - line = get_new_settings_gradle_line("androidx.foo", "foo") - self.assertEqual("includeProject(\":foo:foo\", [BuildType.MAIN])\n", line) - - line = get_new_settings_gradle_line("androidx.foo", "foo-bar") - self.assertEqual("includeProject(\":foo:foo-bar\", [BuildType.MAIN])\n", line) - - line = get_new_settings_gradle_line("androidx.foo.bar", "bar") - self.assertEqual("includeProject(\":foo:bar:bar\", [BuildType.MAIN])\n", line) - - line = get_new_settings_gradle_line("androidx.foo.bar", "bar-qux") - self.assertEqual("includeProject(\":foo:bar:bar-qux\", [BuildType.MAIN])\n", line) - - line = get_new_settings_gradle_line("androidx.compose", "compose-foo") - self.assertEqual("includeProject(\":compose:compose-foo\", [BuildType.COMPOSE])\n", line) - - line = get_new_settings_gradle_line("androidx.compose.foo", "foo-bar") - self.assertEqual("includeProject(\":compose:foo:foo-bar\", [BuildType.COMPOSE])\n", line) - - line = get_new_settings_gradle_line("androidx.foo.bar", "bar-compose") - self.assertEqual("includeProject(\":foo:bar:bar-compose\", [BuildType.COMPOSE])\n", line) - - def test_gradle_project_coordinates(self): - coordinates = get_gradle_project_coordinates("androidx.foo", "foo") - self.assertEqual(":foo:foo", coordinates) - - coordinates = get_gradle_project_coordinates("androidx.foo", "foo-bar") - self.assertEqual(":foo:foo-bar", coordinates) - - coordinates = get_gradle_project_coordinates("androidx.foo.bar", "bar") - self.assertEqual(":foo:bar:bar", coordinates) - - coordinates = get_gradle_project_coordinates("androidx.foo.bar", "bar-qux") - self.assertEqual(":foo:bar:bar-qux", coordinates) - -class TestBuildGradle(unittest.TestCase): - def test_correct_library_type_is_returned(self): - library_type = get_library_type("foo-samples") - self.assertEqual("SAMPLES", library_type) - - library_type = get_library_type("foo-compiler") - self.assertEqual("ANNOTATION_PROCESSOR", library_type) - - library_type = get_library_type("foo-lint") - self.assertEqual("LINT", library_type) - - library_type = get_library_type("foo-inspection") - self.assertEqual("IDE_PLUGIN", library_type) - - library_type = get_library_type("foo") - self.assertEqual("PUBLISHED_LIBRARY", library_type) - - library_type = get_library_type("foo-inspect") - self.assertEqual("PUBLISHED_LIBRARY", library_type) - - library_type = get_library_type("foocomp") - self.assertEqual("PUBLISHED_LIBRARY", library_type) - - library_type = get_library_type("foo-bar") - self.assertEqual("PUBLISHED_LIBRARY", library_type) - - -class TestDocsTipOfTree(unittest.TestCase): - - def test_docs_tip_of_tree_build_grade_line(self): - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo", "foo") - self.assertEqual(" docs(project(\":foo:foo\"))\n", line) - - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo", "foo-bar") - self.assertEqual(" docs(project(\":foo:foo-bar\"))\n", line) - - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo.bar", "bar") - self.assertEqual(" docs(project(\":foo:bar:bar\"))\n", line) - - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo.bar", "bar-qux") - self.assertEqual(" docs(project(\":foo:bar:bar-qux\"))\n", line) - - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo", "foo-samples") - self.assertIsNone(line) - - line = get_new_docs_tip_of_tree_build_grade_line("androidx.foo.bar", "bar-qux-samples") - self.assertIsNone(line) - -class TestReplacements(unittest.TestCase): - - def test_version_macro(self): - macro = get_group_id_version_macro("androidx.foo") - self.assertEqual("FOO", macro) - - macro = get_group_id_version_macro("androidx.foo.bar") - self.assertEqual("FOO_BAR", macro) - - macro = get_group_id_version_macro("androidx.compose.bar") - self.assertEqual("BAR", macro) - - macro = get_group_id_version_macro("androidx.compose.foo.bar") - self.assertEqual("FOO_BAR", macro) - - macro = get_group_id_version_macro("androidx.compose") - self.assertEqual("COMPOSE", macro) - - def test_sed(self): - out_dir = "./out" - test_file = out_dir + "/temp.txt" - test_file_contents = "a\nb\nc" - if not os.path.exists(out_dir): - os.makedirs(out_dir) - with open(test_file,"w") as f: - f.write("a\nb\nc") - sed("a", "d", test_file) - - # write back the file - with open(test_file) as f: - file_contents = f.read() - self.assertEqual("d\nb\nc", file_contents) - rm(out_dir) - - def test_mv_dir_within_same_dir(self): - src_out_dir = "./src_out" - test_src_file = src_out_dir + "/temp.txt" - test_file_contents = "a\nb\nc" - if not os.path.exists(src_out_dir): - os.makedirs(src_out_dir) - with open(test_src_file,"w") as f: - f.write("a\nb\nc") - - dst_out_dir = "./dst_out" - mv_dir(src_out_dir, dst_out_dir) - # write back the file - with open(dst_out_dir + "/temp.txt") as f: - file_contents = f.read() - self.assertEqual("a\nb\nc", file_contents) - rm(src_out_dir) - rm(dst_out_dir) - - def test_mv_dir_to_different_dir(self): - src_out_dir = "./src_out_2" - test_src_file = src_out_dir + "/temp.txt" - test_file_contents = "a\nb\nc" - if not os.path.exists(src_out_dir): - os.makedirs(src_out_dir) - with open(test_src_file,"w") as f: - f.write("a\nb\nc") - - dst_out_dir_parent = "./dst_out_2" - dst_out_dir = dst_out_dir_parent + "/hello/world" - mv_dir(src_out_dir, dst_out_dir) - # write back the file - with open(dst_out_dir + "/temp.txt") as f: - file_contents = f.read() - self.assertEqual("a\nb\nc", file_contents) - rm(src_out_dir) - rm(dst_out_dir_parent) - - def test_rename_file_within_same_dir(self): - test_src_file = "./temp.txt" - test_file_contents = "a\nb\nc" - with open(test_src_file,"w") as f: - f.write("a\nb\nc") - - test_dst_file = "./temp_out.txt" - rename_file(test_src_file, test_dst_file) - # read back the file - with open(test_dst_file) as f: - file_contents = f.read() - self.assertEqual("a\nb\nc", file_contents) - rm(test_dst_file) - - def test_remove_line(self): - out_dir = "./out" - test_file = out_dir + "/temp.txt" - test_file_contents = "a\nb\nc" - if not os.path.exists(out_dir): - os.makedirs(out_dir) - - with open(test_file,"w") as f: - f.write("a\nb\nc") - remove_line("b", test_file) - # read back the file and check - with open(test_file) as f: - file_contents = f.read() - self.assertEqual("a\nc", file_contents) - - with open(test_file,"w") as f: - f.write("abc\ndef\nghi") - remove_line("c", test_file) - # read back the file and check - with open(test_file) as f: - file_contents = f.read() - self.assertEqual("def\nghi", file_contents) - - # Clean up - rm(out_dir) - - -class TestLibraryGroupKt(unittest.TestCase): - - def test_library_group_atomicity_is_correctly_determined(self): - self.assertFalse(is_group_id_atomic("androidx.core")) - self.assertFalse(is_group_id_atomic("androidx.foo")) - self.assertFalse(is_group_id_atomic("")) - self.assertFalse(is_group_id_atomic("androidx.compose.foo")) - self.assertTrue(is_group_id_atomic("androidx.cardview")) - self.assertTrue(is_group_id_atomic("androidx.tracing")) - self.assertTrue(is_group_id_atomic("androidx.compose.foundation")) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/libraryversions.toml b/libraryversions.toml index d848ceb0bb6e3..6adcdea80e10c 100644 --- a/libraryversions.toml +++ b/libraryversions.toml @@ -70,7 +70,7 @@ EXIFINTERFACE = "1.4.0-rc01" FRAGMENT = "1.9.0-alpha01" FUTURES = "1.3.0-rc01" GLANCE = "1.3.0-alpha01" -GLANCE_WEAR = "1.0.0-alpha01" +GLANCE_WEAR = "1.0.0-alpha02" GLANCE_WEAR_TILES = "1.0.0-alpha07" GRAPHICS_CORE = "1.0.0" GRAPHICS_FILTERS = "1.0.0-alpha01" @@ -119,7 +119,7 @@ RECOMMENDATION = "1.1.0-alpha01" RECYCLERVIEW = "1.5.0-alpha01" RECYCLERVIEW_SELECTION = "1.3.0-alpha01" REMOTECALLBACK = "1.0.0-alpha03" -REMOTECOMPOSE = "1.0.0-alpha02" +REMOTECOMPOSE = "1.0.0-alpha03" REMOTECOMPOSE_WEAR = "1.0.0-alpha01" RESOURCEINSPECTION = "1.1.0-alpha01" ROOM3 = "3.0.0-alpha01" @@ -167,9 +167,9 @@ WEAR_INPUT = "1.2.0-rc01" WEAR_INPUT_TESTING = "1.2.0-rc01" WEAR_ONGOING = "1.1.0-rc01" WEAR_PHONE_INTERACTIONS = "1.1.0-rc01" -WEAR_PROTOLAYOUT = "1.4.0-alpha04" +WEAR_PROTOLAYOUT = "1.4.0-alpha05" WEAR_REMOTE_INTERACTIONS = "1.2.0-rc01" -WEAR_TILES = "1.6.0-alpha04" +WEAR_TILES = "1.6.0-alpha05" WEAR_TOOLING_PREVIEW = "1.0.0-rc01" WEAR_WATCHFACE = "1.3.0-rc01" WEAR_WATCHFACEPUSH = "1.0.0-rc01" diff --git a/wear/tiles/tiles-renderer/lint-baseline.xml b/wear/tiles/tiles-renderer/lint-baseline.xml index 46c9dafd1ef4f..2090255b36d74 100644 --- a/wear/tiles/tiles-renderer/lint-baseline.xml +++ b/wear/tiles/tiles-renderer/lint-baseline.xml @@ -777,7 +777,7 @@ mediaBlendingMode + else -> + throw IllegalArgumentException( + "Unsupported value for ImpressApi.MediaBlendingMode: $mediaBlendingMode" + ) + } + /* * This is mostly here to throw on unsupported values. The int cast works as long as * ImpressApi.ContentSecurityLevel is in sync with imp::ContentSecurityLevel. @@ -465,30 +476,29 @@ public class ImpressApiImpl : ImpressApi { ) override fun createStereoSurface(@StereoMode stereoMode: Int): ImpressNode = - ImpressNode( - nCreateStereoSurfaceEntity( - getViewNativeHandle(view), - validateStereoMode(stereoMode), - ContentSecurityLevel.NONE, - /* useSuperSampling= */ false, - ) - ) + createStereoSurface(stereoMode, ContentSecurityLevel.NONE) override fun createStereoSurface( @StereoMode stereoMode: Int, @ContentSecurityLevel contentSecurityLevel: Int, ): ImpressNode = - ImpressNode( - nCreateStereoSurfaceEntity( - getViewNativeHandle(view), - validateStereoMode(stereoMode), - validateContentSecurityLevel(contentSecurityLevel), - /* useSuperSampling= */ false, - ) + createStereoSurface(stereoMode, contentSecurityLevel, /* useSuperSampling= */ false) + + override fun createStereoSurface( + @StereoMode stereoMode: Int, + @ContentSecurityLevel contentSecurityLevel: Int, + useSuperSampling: Boolean, + ): ImpressNode = + createStereoSurface( + stereoMode, + MediaBlendingMode.TRANSPARENT, + contentSecurityLevel, + useSuperSampling, ) override fun createStereoSurface( @StereoMode stereoMode: Int, + @MediaBlendingMode mediaBlendingMode: Int, @ContentSecurityLevel contentSecurityLevel: Int, useSuperSampling: Boolean, ): ImpressNode = @@ -496,6 +506,7 @@ public class ImpressApiImpl : ImpressApi { nCreateStereoSurfaceEntity( getViewNativeHandle(view), validateStereoMode(stereoMode), + validateMediaBlendingMode(mediaBlendingMode), validateContentSecurityLevel(contentSecurityLevel), useSuperSampling, ) @@ -1513,6 +1524,7 @@ public class ImpressApiImpl : ImpressApi { private external fun nCreateStereoSurfaceEntity( view: Long, stereoMode: Int, + blendingMode: Int, contentSecurityLevel: Int, useSuperSampling: Boolean, ): Int diff --git a/xr/scenecore/scenecore-spatial-rendering/src/test/java/androidx/xr/scenecore/impl/impress/FakeImpressApiImplTest.kt b/xr/scenecore/scenecore-spatial-rendering/src/test/java/androidx/xr/scenecore/impl/impress/FakeImpressApiImplTest.kt index 3af71234fb1b6..155f388c3d387 100644 --- a/xr/scenecore/scenecore-spatial-rendering/src/test/java/androidx/xr/scenecore/impl/impress/FakeImpressApiImplTest.kt +++ b/xr/scenecore/scenecore-spatial-rendering/src/test/java/androidx/xr/scenecore/impl/impress/FakeImpressApiImplTest.kt @@ -18,6 +18,8 @@ package androidx.xr.scenecore.impl.impress import androidx.xr.scenecore.impl.impress.FakeImpressApiImpl.StereoSurfaceEntityData import androidx.xr.scenecore.impl.impress.FakeImpressApiImpl.StereoSurfaceEntityData.CanvasShape +import androidx.xr.scenecore.impl.impress.ImpressApi.ContentSecurityLevel +import androidx.xr.scenecore.impl.impress.ImpressApi.MediaBlendingMode import androidx.xr.scenecore.impl.impress.ImpressApi.StereoMode import androidx.xr.scenecore.runtime.KhronosPbrMaterialSpec import androidx.xr.scenecore.runtime.TextureSampler @@ -311,6 +313,24 @@ class FakeImpressApiImplTest { assertThat(surface).isNotNull() } + @Test + fun createStereoSurface_withBlendingMode_createsStereoSurface() { + val stereoMode = StereoMode.MONO + val blendingMode = MediaBlendingMode.OPAQUE + val contentSecurityLevel = ContentSecurityLevel.NONE + val stereoSurfaceNode = + fakeImpressApi.createStereoSurface( + stereoMode, + blendingMode, + contentSecurityLevel, + useSuperSampling = false, + ) + val stereoSurface = fakeImpressApi.getStereoSurfaceEntities() + val stereoSurfaceData = stereoSurface[stereoSurfaceNode] + assertNotNull(stereoSurfaceData) + assertThat(stereoSurfaceData.mediaBlendingMode).isEqualTo(blendingMode) + } + @Test fun setStereoSurfaceEntityCanvasShapeQuad_setsCanvasShapeQuad() { val stereoMode = StereoMode.MONO